<?php

use Core\Controller;
use Core\Auth;

/**
 * AuthController — Handles all authentication flows
 * Login, OTP setup/verify, password reset, logout
 */
class AuthController extends Controller
{
    private UserAuthModel $userModel;

    public function __construct()
    {
        parent::__construct();
        $this->userModel = new UserAuthModel();
    }

    // ─────────────────────────────────────────────
    // LOGIN
    // ─────────────────────────────────────────────

    public function showLogin(): void
    {
        if (Auth::check()) {
            $this->redirect(APP_URL . '/dashboard');
        }
        $this->render('login', [], 'auth');
    }

    public function handleLogin(): void
    {
        if (!$this->validateCsrf()) {
            $this->setFlash('error', 'Invalid security token. Please try again.');
            $this->redirect(APP_URL . '/login');
        }

        $email    = strtolower(trim($this->getPost('email', '')));
        $password = $this->getPost('password', '');

        if (empty($email) || empty($password)) {
            $this->setFlash('error', 'Email and password are required.');
            $this->redirect(APP_URL . '/login');
        }

        // Check lockout
        if ($this->userModel->isLockedOut($email)) {
            $this->setFlash('error', 'Account temporarily locked due to too many failed attempts. Try again in 15 minutes.');
            $this->redirect(APP_URL . '/login');
        }

        $user = $this->userModel->findByEmail($email);

        if (!$user || !password_verify($password, $user['password'])) {
            $this->userModel->incrementLoginAttempts($email);
            logAudit('login_failed', 'auth', 'user', null, null, ['email' => $email]);
            $this->setFlash('error', 'Invalid email or password.');
            $this->redirect(APP_URL . '/login');
        }

        if (!$user['is_active']) {
            $this->setFlash('error', 'Your account has been disabled. Please contact support.');
            $this->redirect(APP_URL . '/login');
        }

        // Reset attempts on success
        $this->userModel->resetLoginAttempts($email);
        $this->userModel->updateLastLogin($user['id']);

        // Store user in session (partial — pending OTP if enabled)
        Auth::setOtpPending($user['id']);

        // Check OTP
        $otpSecret = $this->userModel->getOtpSecret($user['id']);
        if ($otpSecret && $otpSecret['is_enabled']) {
            $this->redirect(APP_URL . '/otp/verify');
        }

        // OTP not enabled — complete login
        Auth::login($user);
        Auth::clearOtpPending();
        logAudit('login_success', 'auth', 'user', $user['id']);
        $this->redirect(APP_URL . '/dashboard');
    }

    // ─────────────────────────────────────────────
    // ORGANIZATION SIGNUP
    // ─────────────────────────────────────────────

    public function showSignup(): void
    {
        if (Auth::check()) {
            $this->redirect(APP_URL . '/dashboard');
        }
        $this->render('signup', [
            'pageTitle' => 'Create Your Organization',
            'old'       => $this->getOldInput(),
        ], 'auth');
    }

    public function handleSignup(): void
    {
        $orgName   = trim($this->getPost('org_name', ''));
        $industry  = trim($this->getPost('industry', ''));
        $firstName = trim($this->getPost('first_name', ''));
        $lastName  = trim($this->getPost('last_name', ''));
        $email     = strtolower(trim($this->getPost('email', '')));
        $phone     = trim($this->getPost('phone', ''));
        $password  = $this->getPost('password', '');
        $confirm   = $this->getPost('password_confirm', '');

        // Never stash the password fields — everything else is safe to refill.
        $oldInput = [
            'org_name'   => $orgName,
            'industry'   => $industry,
            'first_name' => $firstName,
            'last_name'  => $lastName,
            'email'      => $email,
            'phone'      => $phone,
        ];

        if (!$this->validateCsrf()) {
            $this->setOldInput($oldInput);
            $this->setFlash('error', 'Invalid security token. Please try again.');
            $this->redirect(APP_URL . '/signup');
        }

        if (empty($orgName) || empty($firstName) || empty($lastName) || empty($email) || empty($password)) {
            $this->setOldInput($oldInput);
            $this->setFlash('error', 'Please fill in all required fields.');
            $this->redirect(APP_URL . '/signup');
        }

        if (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
            $this->setOldInput($oldInput);
            $this->setFlash('error', 'Please enter a valid email address.');
            $this->redirect(APP_URL . '/signup');
        }

        $passError = $this->validatePassword($password, $confirm);
        if ($passError) {
            $this->setOldInput($oldInput);
            $this->setFlash('error', $passError);
            $this->redirect(APP_URL . '/signup');
        }

        if ($this->userModel->findByEmail($email)) {
            $this->setOldInput($oldInput);
            $this->setFlash('error', 'An account with that email already exists.');
            $this->redirect(APP_URL . '/signup');
        }

        $slug = $this->generateUniqueSlug($orgName);

        $this->userModel->beginTransaction();
        try {
            // Organizations no longer require manual review — accounts are
            // active immediately, starting on a 7-day trial of the platform's
            // cheapest active plan (whatever the admin has configured).
            $trialPlanId = \Core\Pricing::defaultPlanId($this->db);
            $orgId = $this->dbInsertReturningId(
                "INSERT INTO organizations (name, slug, industry, email, phone, locale, currency, timezone, status, is_active, approved_at, plan_id, subscription_status, trial_ends_at, created_at, updated_at)
                 VALUES (?, ?, ?, ?, ?, ?, ?, ?, 'approved', 1, NOW(), ?, 'trial', DATE_ADD(NOW(), INTERVAL 7 DAY), NOW(), NOW())",
                [$orgName, $slug, $industry ?: null, $email, $phone ?: null, DEFAULT_LOCALE, DEFAULT_CURRENCY, DEFAULT_TIMEZONE, $trialPlanId]
            );

            $userId = $this->userModel->createUser([
                'org_id'      => $orgId,
                'entity_type' => 'org_user',
                'first_name'  => $firstName,
                'last_name'   => $lastName,
                'email'       => $email,
                'phone'       => $phone ?: null,
                'password'    => $password,
                'is_active'   => 1,
            ]);

            if (!$orgId || !$userId) {
                throw new \RuntimeException('Failed to create organization or user.');
            }

            // Grant the org creator the admin role so their dashboard/permissions
            // work right away instead of waiting on a super-admin to assign one.
            $adminRole = $this->dbQueryOne("SELECT id FROM roles WHERE name = 'admin' AND is_system = 1");
            if ($adminRole) {
                $this->dbExecute("INSERT INTO user_roles (user_id, role_id) VALUES (?, ?)", [$userId, $adminRole['id']]);
            }

            $this->userModel->commit();
        } catch (\Throwable $e) {
            $this->userModel->rollback();
            $this->setOldInput($oldInput);
            $this->setFlash('error', 'Something went wrong while creating your account. Please try again.');
            $this->redirect(APP_URL . '/signup');
        }

        logAudit('organization_signup', 'auth', 'organization', $orgId, null, ['org_name' => $orgName, 'email' => $email]);

        $htmlBody = $this->renderEmailTemplate('org_signup_received', [
            'userName'   => $firstName,
            'orgName'    => $orgName,
            'dashboardUrl' => APP_URL . '/dashboard',
        ]);
        sendEmail($email, $firstName . ' ' . $lastName, 'Welcome to ' . APP_NAME . ' — your organization is ready', $htmlBody);

        // Sign the user straight in and drop them on their dashboard —
        // no waiting on a review step.
        $user = $this->userModel->findByIdWithOrg($userId);
        $this->userModel->updateLastLogin($userId);
        Auth::login($user);
        Auth::clearOtpPending();
        logAudit('login_success', 'auth', 'user', $userId);

        $this->setFlash('success', 'Welcome to ' . APP_NAME . '! Your organization is all set up.');
        $this->redirect(APP_URL . '/dashboard');
    }

    private function generateUniqueSlug(string $name): string
    {
        $base = strtolower(trim(preg_replace('/[^a-zA-Z0-9]+/', '-', $name), '-'));
        $base = $base ?: 'org';
        $slug = $base;
        $i = 1;
        while ($this->dbQueryOne("SELECT id FROM organizations WHERE slug = ?", [$slug])) {
            $slug = $base . '-' . (++$i);
        }
        return $slug;
    }

    private function dbInsertReturningId(string $sql, array $params): int|false
    {
        if (!$this->dbExecute($sql, $params)) {
            return false;
        }
        return (int)$this->db->lastInsertId();
    }

    // ─────────────────────────────────────────────
    // OTP SETUP
    // ─────────────────────────────────────────────

    public function showOtpSetup(): void
    {
        $this->requireAuth();
        $user = Auth::user();

        try {
            $tfa    = new \RobThree\Auth\TwoFactorAuth(issuer: OTP_ISSUER, qrcodeprovider: new \RobThree\Auth\Providers\Qr\BaconQrCodeProvider(format: 'svg'));
            $secret = $tfa->createSecret(160);

            // Store secret temporarily in session until verified
            $_SESSION['otp_temp_secret'] = $secret;

            $qrCodeUrl  = $tfa->getQRCodeImageAsDataUri($user['email'], $secret);
            $backupCodes = $this->generateBackupCodes();

            $_SESSION['otp_temp_backup_codes'] = $backupCodes;

            $this->render('otp_setup', [
                'qrCodeUrl'   => $qrCodeUrl,
                'secret'      => $secret,
                'backupCodes' => $backupCodes,
                'pageTitle'   => 'Set Up Two-Factor Authentication',
            ], 'auth');
        } catch (\Exception $e) {
            $this->setFlash('error', 'Failed to generate OTP secret. Please try again.');
            $this->redirect(APP_URL . '/dashboard');
        }
    }

    public function handleOtpSetup(): void
    {
        $this->requireAuth();
        if (!$this->validateCsrf()) {
            $this->setFlash('error', 'Invalid security token.');
            $this->redirect(APP_URL . '/otp/setup');
        }

        $code   = trim($this->getPost('otp_code', ''));
        $secret = $_SESSION['otp_temp_secret'] ?? null;

        if (!$secret) {
            $this->setFlash('error', 'Session expired. Please start OTP setup again.');
            $this->redirect(APP_URL . '/otp/setup');
        }

        try {
            $tfa = new \RobThree\Auth\TwoFactorAuth(issuer: OTP_ISSUER, qrcodeprovider: new \RobThree\Auth\Providers\Qr\BaconQrCodeProvider(format: 'svg'));
            if (!$tfa->verifyCode($secret, $code)) {
                $this->setFlash('error', 'Invalid verification code. Please try again.');
                $this->redirect(APP_URL . '/otp/setup');
            }

            $backupCodes = $_SESSION['otp_temp_backup_codes'] ?? $this->generateBackupCodes();
            $user        = Auth::user();

            $this->userModel->saveOtpSecret($user['id'], $secret, $backupCodes);
            $this->userModel->enableOtp($user['id']);

            unset($_SESSION['otp_temp_secret'], $_SESSION['otp_temp_backup_codes']);

            logAudit('otp_enabled', 'auth', 'user', $user['id']);
            $this->setFlash('success', 'Two-factor authentication has been enabled successfully.');
            $this->redirect(APP_URL . '/settings/security');
        } catch (\Exception $e) {
            $this->setFlash('error', 'Failed to enable OTP: ' . $e->getMessage());
            $this->redirect(APP_URL . '/otp/setup');
        }
    }

    // ─────────────────────────────────────────────
    // OTP VERIFY (during login)
    // ─────────────────────────────────────────────

    public function showOtpVerify(): void
    {
        if (!Auth::isPendingOtp()) {
            $this->redirect(APP_URL . '/login');
        }
        $this->render('otp_verify', ['pageTitle' => 'Verify Your Identity'], 'auth');
    }

    public function handleOtpVerify(): void
    {
        if (!Auth::isPendingOtp()) {
            $this->redirect(APP_URL . '/login');
        }
        if (!$this->validateCsrf()) {
            $this->setFlash('error', 'Invalid security token.');
            $this->redirect(APP_URL . '/otp/verify');
        }

        $otpUserId = $_SESSION['otp_user_id'] ?? null;
        $pendingUser = $otpUserId ? $this->userModel->findByIdWithOrg($otpUserId) : null;
        if (!$pendingUser) {
            $this->redirect(APP_URL . '/login');
        }

        $code       = trim($this->getPost('otp_code', ''));
        $backupCode = trim($this->getPost('backup_code', ''));
        $otpSecret  = $this->userModel->getOtpSecret($pendingUser['id']);

        if (!$otpSecret) {
            Auth::login($pendingUser);
            Auth::clearOtpPending();
            $this->redirect(APP_URL . '/dashboard');
        }

        // Backup code path
        if (!empty($backupCode)) {
            if ($this->userModel->verifyBackupCode($pendingUser['id'], $backupCode)) {
                Auth::login($pendingUser);
                Auth::clearOtpPending();
                logAudit('otp_backup_used', 'auth', 'user', $pendingUser['id']);
                $this->setFlash('warning', 'Backup code used. Please set up OTP again if you have run out of codes.');
                $this->redirect(APP_URL . '/dashboard');
            } else {
                $this->setFlash('error', 'Invalid backup code.');
                $this->redirect(APP_URL . '/otp/verify');
            }
        }

        // TOTP path
        try {
            $tfa = new \RobThree\Auth\TwoFactorAuth(issuer: OTP_ISSUER, qrcodeprovider: new \RobThree\Auth\Providers\Qr\BaconQrCodeProvider(format: 'svg'));
            if ($tfa->verifyCode($otpSecret['secret'], $code)) {
                Auth::login($pendingUser);
                Auth::clearOtpPending();
                logAudit('login_otp_success', 'auth', 'user', $pendingUser['id']);
                $this->redirect(APP_URL . '/dashboard');
            } else {
                $this->setFlash('error', 'Invalid OTP code. Please try again.');
                $this->redirect(APP_URL . '/otp/verify');
            }
        } catch (\Exception $e) {
            $this->setFlash('error', 'OTP verification failed. Please try again.');
            $this->redirect(APP_URL . '/otp/verify');
        }
    }

    // ─────────────────────────────────────────────
    // FORGOT / RESET PASSWORD
    // ─────────────────────────────────────────────

    public function showForgotPassword(): void
    {
        if (Auth::check()) {
            $this->redirect(APP_URL . '/dashboard');
        }
        $this->render('forgot_password', ['pageTitle' => 'Reset Your Password'], 'auth');
    }

    public function handleForgotPassword(): void
    {
        if (!$this->validateCsrf()) {
            $this->setFlash('error', 'Invalid security token.');
            $this->redirect(APP_URL . '/forgot-password');
        }

        $email = strtolower(trim($this->getPost('email', '')));
        if (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
            $this->setFlash('error', 'Please enter a valid email address.');
            $this->redirect(APP_URL . '/forgot-password');
        }

        // Always show success to prevent email enumeration
        $user = $this->userModel->findByEmail($email);
        if ($user && $user['is_active']) {
            $token     = generateToken(48);
            $expiresAt = date('Y-m-d H:i:s', strtotime('+1 hour'));

            $this->dbExecute(
                "INSERT INTO password_reset_tokens (email, token, expires_at, created_at) VALUES (?, ?, ?, NOW())",
                [$email, $token, $expiresAt]
            );

            $resetUrl = APP_URL . '/reset-password/' . $token;
            $htmlBody = $this->renderEmailTemplate('reset_password', [
                'userName' => $user['first_name'],
                'resetUrl' => $resetUrl,
                'expiresIn' => '1 hour',
            ]);

            sendEmail($email, $user['first_name'] . ' ' . $user['last_name'], 'Password Reset Request', $htmlBody);
        }

        $this->setFlash('success', 'If that email is registered, you will receive a password reset link shortly.');
        $this->redirect(APP_URL . '/forgot-password');
    }

    public function showResetPassword(string $token): void
    {
        $reset = $this->dbQueryOne(
            "SELECT * FROM password_reset_tokens WHERE token = ? AND expires_at > NOW() AND used_at IS NULL",
            [$token]
        );

        if (!$reset) {
            $this->setFlash('error', 'This reset link is invalid or has expired. Please request a new one.');
            $this->redirect(APP_URL . '/forgot-password');
        }

        $this->render('reset_password', ['token' => $token, 'pageTitle' => 'Set New Password'], 'auth');
    }

    public function handleResetPassword(): void
    {
        if (!$this->validateCsrf()) {
            $this->setFlash('error', 'Invalid security token.');
            $this->redirect(APP_URL . '/forgot-password');
        }

        $token    = $this->getPost('token', '');
        $password = $this->getPost('password', '');
        $confirm  = $this->getPost('password_confirm', '');

        $reset = $this->dbQueryOne(
            "SELECT * FROM password_reset_tokens WHERE token = ? AND expires_at > NOW() AND used_at IS NULL",
            [$token]
        );

        if (!$reset) {
            $this->setFlash('error', 'This reset link is invalid or has expired.');
            $this->redirect(APP_URL . '/forgot-password');
        }

        $passError = $this->validatePassword($password, $confirm);
        if ($passError) {
            $this->setFlash('error', $passError);
            $this->redirect(APP_URL . '/reset-password/' . $token);
        }

        $hashed = password_hash($password, PASSWORD_BCRYPT);
        $user   = $this->userModel->findByEmail($reset['email']);

        if ($user) {
            $this->userModel->update($user['id'], ['password' => $hashed]);
            $this->dbExecute(
                "UPDATE password_reset_tokens SET used_at = NOW() WHERE token = ?",
                [$token]
            );
            logAudit('password_reset', 'auth', 'user', $user['id']);
        }

        $this->setFlash('success', 'Password reset successfully. You can now log in.');
        $this->redirect(APP_URL . '/login');
    }

    // ─────────────────────────────────────────────
    // LOGOUT
    // ─────────────────────────────────────────────

    public function logout(): void
    {
        $userId = Auth::id();
        $isIdle = $this->getGet('idle', '') === '1';
        Auth::logout();
        if ($userId) {
            logAudit($isIdle ? 'logout_idle' : 'logout', 'auth', 'user', $userId);
        }
        if ($isIdle) {
            $this->setFlash('warning', 'You were logged out after 5 minutes of inactivity. Please sign in again.');
        }
        $this->redirect(APP_URL . '/login');
    }

    // ─────────────────────────────────────────────
    // PRIVATE HELPERS
    // ─────────────────────────────────────────────

    private function generateBackupCodes(): array
    {
        $codes = [];
        for ($i = 0; $i < 8; $i++) {
            $codes[] = strtoupper(bin2hex(random_bytes(4)));
        }
        return $codes;
    }

    private function validatePassword(string $password, string $confirm): ?string
    {
        if (strlen($password) < 8) {
            return 'Password must be at least 8 characters long.';
        }
        if (!preg_match('/[A-Z]/', $password)) {
            return 'Password must contain at least one uppercase letter.';
        }
        if (!preg_match('/[a-z]/', $password)) {
            return 'Password must contain at least one lowercase letter.';
        }
        if (!preg_match('/[0-9]/', $password)) {
            return 'Password must contain at least one number.';
        }
        if (!preg_match('/[^A-Za-z0-9]/', $password)) {
            return 'Password must contain at least one special character.';
        }
        if ($password !== $confirm) {
            return 'Passwords do not match.';
        }
        return null;
    }

    private function renderEmailTemplate(string $template, array $data): string
    {
        extract($data);
        ob_start();
        $templatePath = ROOT_PATH . '/modules/auth/views/email_templates/' . $template . '.php';
        if (file_exists($templatePath)) {
            include $templatePath;
        }
        return ob_get_clean();
    }
}
