<?php

namespace App\Http\Controllers\Admin;

use App\Core\Controller;
use App\Core\RateLimiter;
use App\Core\Request;
use App\Core\Response;
use App\Models\PasswordReset;
use App\Models\Setting;
use App\Models\User;
use App\Services\ActivityLogger;
use App\Services\Mailer;
use App\Services\QrCode;
use App\Services\TotpService;

class AuthController extends Controller
{
    // ─────────────────────────────────────────────────────────
    // Login / Logout
    // ─────────────────────────────────────────────────────────

    public function loginForm(Request $request, array $params = []): void
    {
        if ($this->auth()->check()) {
            Response::redirect(admin_url('dashboard'));
            return;
        }

        $view = $this->app->service('view');
        $html = $view->renderAdmin('auth/login', [
            'error'      => $this->session()->getFlash('login_error'),
            'reset_done' => $this->session()->getFlash('reset_done'),
            'csrf'       => $this->csrf(),
        ]);
        Response::html($html);
    }

    public function login(Request $request, array $params = []): void
    {
        $this->validateCsrf();

        $username   = trim($request->input('username', ''));
        $password   = $request->input('password', '');
        $rememberMe = (bool) $request->input('remember_me', false);

        // Rate limiting: key by IP + username to prevent brute-force
        $ip        = $_SERVER['REMOTE_ADDR'] ?? '0.0.0.0';
        $throttle  = new RateLimiter($this->app->basePath('storage/cache'));
        $throttleKey = 'login:' . $ip . ':' . strtolower($username);

        if ($throttle->isLocked($throttleKey)) {
            $remaining = $throttle->lockoutRemaining($throttleKey);
            $minutes   = (int) ceil($remaining / 60);
            $this->session()->flash('login_error', __('messages.login_locked', ['minutes' => $minutes]));
            Response::redirect(admin_url('login'));
            return;
        }

        // Verify credentials without establishing session
        $user = $this->auth()->verifyCredentials($username, $password);

        if (!$user) {
            $locked = $throttle->recordFailure($throttleKey);
            if ($locked) {
                $minutes = (int) ceil($throttle->lockoutRemaining($throttleKey) / 60);
                $this->session()->flash('login_error', __('messages.login_locked', ['minutes' => $minutes]));
            } else {
                $this->session()->flash('login_error', __('messages.login_error'));
            }
            Response::redirect(admin_url('login'));
            return;
        }

        $throttle->clear($throttleKey);

        // Check if TOTP 2FA is enabled for this user
        $totpAvailable = Setting::get('totp_2fa_available', '0') === '1';
        if ($totpAvailable && !empty($user['totp_enabled'])) {
            // Store pending 2FA state and redirect to TOTP verify
            $this->session()->set('pending_2fa_user_id', (int) $user['id']);
            $this->session()->set('pending_2fa_remember', $rememberMe);
            Response::redirect(admin_url('login/verify-totp'));
            return;
        }

        // No 2FA – complete login
        $this->auth()->loginUser($user, $rememberMe);
        ActivityLogger::log('login', 'user', null, $username);
        Response::redirect(admin_url('dashboard'));
    }

    public function logout(Request $request, array $params = []): void
    {
        ActivityLogger::log('logout', 'user', null, null);
        $this->auth()->logout();
        Response::redirect(admin_url('login'));
    }

    // ─────────────────────────────────────────────────────────
    // Forgot Password – step 1: request reset link
    // ─────────────────────────────────────────────────────────

    public function forgotPasswordForm(Request $request, array $params = []): void
    {
        if ($this->auth()->check()) {
            Response::redirect(admin_url('dashboard'));
            return;
        }

        $view = $this->app->service('view');
        $html = $view->renderAdmin('auth/forgot_password', [
            'success' => $this->session()->getFlash('reset_success'),
            'error'   => $this->session()->getFlash('reset_error'),
            'csrf'    => $this->csrf(),
        ]);
        Response::html($html);
    }

    public function forgotPasswordSend(Request $request, array $params = []): void
    {
        $this->validateCsrf();

        $email = trim($request->input('email', ''));

        // Validate email format
        if (empty($email) || !filter_var($email, FILTER_VALIDATE_EMAIL)) {
            $this->session()->flash('reset_error', __('messages.invalid_email'));
            Response::redirect(admin_url('forgot-password'));
            return;
        }

        // Opportunistic housekeeping: purge old expired tokens
        PasswordReset::purgeExpired();

        // Look up the user – but do NOT reveal whether the address exists.
        $user = User::findWhere(['email' => $email, 'is_active' => 1]);

        if ($user) {
            $token    = PasswordReset::createFor($email);
            $resetUrl = $this->buildResetUrl($token);
            $this->sendResetMail($email, $user['username'], $resetUrl);
        }

        // Always show the same success message regardless of whether the
        // address was found – prevents user enumeration attacks.
        $this->session()->flash(
            'reset_success',
            __('messages.reset_sent')
        );
        Response::redirect(admin_url('forgot-password'));
    }

    // ─────────────────────────────────────────────────────────
    // Reset Password – step 2: set new password via token link
    // ─────────────────────────────────────────────────────────

    public function resetPasswordForm(Request $request, array $params = []): void
    {
        if ($this->auth()->check()) {
            Response::redirect(admin_url('dashboard'));
            return;
        }

        $token  = trim($request->input('token', ''));
        $record = ($token !== '') ? PasswordReset::findValid($token) : null;

        $view = $this->app->service('view');
        $html = $view->renderAdmin('auth/reset_password', [
            'token' => $token,
            'valid' => $record !== null,
            'error' => $this->session()->getFlash('reset_pw_error'),
            'csrf'  => $this->csrf(),
        ]);
        Response::html($html);
    }

    public function resetPasswordSave(Request $request, array $params = []): void
    {
        $this->validateCsrf();

        $token    = trim($request->input('token', ''));
        $password = $request->input('password', '');
        $confirm  = $request->input('password_confirm', '');

        // Validate token
        $record = ($token !== '') ? PasswordReset::findValid($token) : null;
        if (!$record) {
            $this->session()->flash(
                'reset_pw_error',
                __('messages.reset_invalid_token')
            );
            Response::redirect(admin_url('reset-password') . '?token=' . urlencode($token));
            return;
        }

        // Validate new password length
        if (strlen($password) < \App\Core\Auth::MIN_PASSWORD_LENGTH) {
            $this->session()->flash('reset_pw_error', __('messages.password_min', ['min' => \App\Core\Auth::MIN_PASSWORD_LENGTH]));
            Response::redirect(admin_url('reset-password') . '?token=' . urlencode($token));
            return;
        }

        // Validate password confirmation
        if ($password !== $confirm) {
            $this->session()->flash('reset_pw_error', __('messages.password_mismatch'));
            Response::redirect(admin_url('reset-password') . '?token=' . urlencode($token));
            return;
        }

        // Find the user by the e-mail stored in the token record
        $user = User::findWhere(['email' => $record['email'], 'is_active' => 1]);
        if (!$user) {
            // Account deactivated between request and reset – treat as expired
            $this->session()->flash(
                'reset_pw_error',
                __('messages.reset_invalid_token')
            );
            Response::redirect(admin_url('reset-password') . '?token=' . urlencode($token));
            return;
        }

        // Update the password hash
        User::updateById((int) $user['id'], [
            'password_hash' => password_hash($password, PASSWORD_DEFAULT),
            'updated_at'    => date('Y-m-d H:i:s'),
        ]);

        // Invalidate all tokens for this address (single-use + cleanup)
        PasswordReset::deleteForEmail($record['email']);

        $this->session()->flash('reset_done', __('messages.reset_success'));
        Response::redirect(admin_url('login'));
    }

    // ─────────────────────────────────────────────────────────
    // TOTP 2FA – Login verification
    // ─────────────────────────────────────────────────────────

    public function totpVerifyForm(Request $request, array $params = []): void
    {
        $userId = $this->session()->get('pending_2fa_user_id');
        if (!$userId) {
            Response::redirect(admin_url('login'));
            return;
        }

        $view = $this->app->service('view');
        $html = $view->renderAdmin('auth/totp_verify', [
            'error' => $this->session()->getFlash('totp_error'),
            'csrf'  => $this->csrf(),
        ]);
        Response::html($html);
    }

    public function totpVerify(Request $request, array $params = []): void
    {
        $this->validateCsrf();

        $userId   = $this->session()->get('pending_2fa_user_id');
        $remember = (bool) $this->session()->get('pending_2fa_remember', false);

        if (!$userId) {
            Response::redirect(admin_url('login'));
            return;
        }

        $code = trim($request->input('totp_code', ''));
        $user = User::find($userId);

        if (!$user || !$user['is_active']) {
            $this->session()->remove('pending_2fa_user_id');
            $this->session()->remove('pending_2fa_remember');
            Response::redirect(admin_url('login'));
            return;
        }

        $secret = $user['totp_secret'] ?? '';
        $verified = false;

        // Try TOTP code first (6-digit)
        if (strlen($code) === 6 && ctype_digit($code)) {
            $verified = TotpService::verify($secret, $code);
        }

        // Try recovery code (format: XXXXXXXX-XXXXXXXX)
        if (!$verified && strlen($code) > 6) {
            $stored    = User::getRecoveryCodes($userId);
            $remaining = TotpService::useRecoveryCode($code, $stored);
            if ($remaining !== null) {
                User::updateRecoveryCodes($userId, $remaining);
                $verified = true;
            }
        }

        if (!$verified) {
            $this->session()->flash('totp_error', __('messages.totp_invalid'));
            Response::redirect(admin_url('login/verify-totp'));
            return;
        }

        // Clear pending state
        $this->session()->remove('pending_2fa_user_id');
        $this->session()->remove('pending_2fa_remember');

        // Complete login
        $this->auth()->loginUser($user, $remember);
        ActivityLogger::log('login', 'user', null, $user['username'] . ' (2FA)');
        Response::redirect(admin_url('dashboard'));
    }

    // ─────────────────────────────────────────────────────────
    // TOTP 2FA – Setup (authenticated)
    // ─────────────────────────────────────────────────────────

    public function setup2fa(Request $request, array $params = []): void
    {
        $userId = $this->auth()->userId();
        $user   = User::find($userId);

        $data = [
            'pageTitle'     => __('admin.totp.title'),
            'totp_enabled'  => !empty($user['totp_enabled']),
            'recovery_count'=> 0,
            'auth'          => $this->auth(),
            'csrf'          => $this->csrf(),
        ];

        if ($data['totp_enabled']) {
            $codes = User::getRecoveryCodes($userId);
            $data['recovery_count'] = count($codes);
        }

        // Show recovery codes from flash (just enabled)
        $newCodes = $this->session()->getFlash('new_recovery_codes');
        if ($newCodes) {
            $data['show_recovery_codes'] = true;
            $data['recovery_codes']      = $newCodes;
            $data['totp_enabled']        = false; // show codes view instead
        }

        if (!$data['totp_enabled'] && empty($data['show_recovery_codes'])) {
            // Generate new secret for setup
            $secret    = TotpService::generateSecret();
            $siteName  = Setting::get('site_name', 'ITSWEBER CMS');
            $otpUri    = TotpService::getOtpAuthUri($secret, $user['username'], $siteName);

            $data['totp_secret'] = $secret;

            try {
                $data['qr_svg'] = QrCode::toSvg($otpUri, 220, 2);
            } catch (\Throwable $e) {
                // Fallback: show URI as text if QR fails
                $data['qr_svg'] = '<p style="color:var(--gray-500);font-size:0.85rem;">'
                                . __('admin.totp.qr_fallback') . '</p>';
            }
        }

        $view = $this->app->service('view');
        $html = $view->renderAdmin('auth/totp_setup', $data);
        Response::html($html);
    }

    public function enable2fa(Request $request, array $params = []): void
    {
        $this->validateCsrf();

        $userId = $this->auth()->userId();
        $secret = trim($request->input('secret', ''));
        $code   = trim($request->input('totp_code', ''));

        if ($secret === '' || $code === '') {
            set_flash('error', __('messages.totp_setup_incomplete'));
            Response::redirect(admin_url('2fa'));
            return;
        }

        if (!TotpService::verify($secret, $code)) {
            set_flash('error', __('messages.totp_invalid'));
            Response::redirect(admin_url('2fa'));
            return;
        }

        $recoveryCodes = TotpService::generateRecoveryCodes(10);
        User::enableTotp($userId, $secret, $recoveryCodes);

        ActivityLogger::log('enable_2fa', 'user', $userId, 'TOTP 2FA enabled');

        // Flash recovery codes so they're shown once
        $this->session()->flash('new_recovery_codes', $recoveryCodes);

        set_flash('success', __('messages.totp_enabled'));
        Response::redirect(admin_url('2fa'));
    }

    public function disable2fa(Request $request, array $params = []): void
    {
        $this->validateCsrf();

        $userId = $this->auth()->userId();
        $code   = trim($request->input('totp_code', ''));
        $secret = User::getTotpSecret($userId);

        if (!$secret || !TotpService::verify($secret, $code)) {
            set_flash('error', __('messages.totp_invalid'));
            Response::redirect(admin_url('2fa'));
            return;
        }

        User::disableTotp($userId);
        ActivityLogger::log('disable_2fa', 'user', $userId, 'TOTP 2FA disabled');

        set_flash('success', __('messages.totp_disabled'));
        Response::redirect(admin_url('2fa'));
    }

    public function regenerateCodes(Request $request, array $params = []): void
    {
        $this->validateCsrf();

        $userId = $this->auth()->userId();
        $code   = trim($request->input('totp_code', ''));
        $secret = User::getTotpSecret($userId);

        if (!$secret || !TotpService::verify($secret, $code)) {
            set_flash('error', __('messages.totp_invalid'));
            Response::redirect(admin_url('2fa'));
            return;
        }

        $newCodes = TotpService::generateRecoveryCodes(10);
        User::updateRecoveryCodes($userId, $newCodes);

        $this->session()->flash('new_recovery_codes', $newCodes);
        set_flash('success', __('messages.totp_codes_regenerated'));
        Response::redirect(admin_url('2fa'));
    }

    // ─────────────────────────────────────────────────────────
    // Private helpers
    // ─────────────────────────────────────────────────────────

    private function buildResetUrl(string $token): string
    {
        $base = rtrim(config('base_url', ''), '/');

        // Detect base URL from server variables when config is not set
        if ($base === '') {
            $scheme = (!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off') ? 'https' : 'http';
            $host   = $_SERVER['HTTP_HOST'] ?? 'localhost';
            $base   = $scheme . '://' . $host;
        }

        return $base . admin_url('reset-password') . '?token=' . urlencode($token);
    }

    private function sendResetMail(string $email, string $username, string $resetUrl): void
    {
        $mailConfig = mail_config();
        $mailer     = new Mailer($mailConfig);
        $ttl        = (int) (PasswordReset::TOKEN_TTL / 60);
        $siteName   = setting('site_name', 'ITSWEBER CMS');

        // Try editable template from DB first
        $rendered = \App\Models\MailTemplate::render('password_reset', [
            'site_name'         => $siteName,
            'username'          => $username,
            'reset_url'         => $resetUrl,
            'token_ttl_minutes' => (string) $ttl,
        ]);

        if ($rendered) {
            $subject   = $rendered['subject'];
            $htmlBody  = $rendered['body'];
            $plainBody = strip_tags(str_replace(['</p>', '<br>', '</tr>'], ["\n\n", "\n", "\n"], $htmlBody));
        } else {
            // Fallback: hardcoded template (pre-migration)
            $subject   = 'Password Reset – ' . $siteName;
            $htmlBody  = $this->buildResetMailHtml($username, $resetUrl);
            $plainBody = $this->buildResetMailText($username, $resetUrl);
        }

        $mailer->sendHtml($email, $subject, $htmlBody, $plainBody);
    }

    private function buildResetMailText(string $username, string $resetUrl): string
    {
        $ttl = (int) (PasswordReset::TOKEN_TTL / 60);
        return "Hello {$username},\n\n"
             . "You (or someone else) requested a password reset for your ITSWEBER CMS account.\n\n"
             . "Click the link below to set a new password (valid for {$ttl} minutes):\n"
             . "{$resetUrl}\n\n"
             . "If you did not request a password reset, you can safely ignore this e-mail.\n"
             . "Your password will not change.\n\n"
             . "– ITSWEBER CMS";
    }

    private function buildResetMailHtml(string $username, string $resetUrl): string
    {
        $ttl      = (int) (PasswordReset::TOKEN_TTL / 60);
        $safeUrl  = htmlspecialchars($resetUrl, ENT_QUOTES, 'UTF-8');
        $safeName = htmlspecialchars($username, ENT_QUOTES, 'UTF-8');

        return '<!DOCTYPE html><html lang="en"><head><meta charset="UTF-8"><title>Password Reset</title></head>'
             . '<body style="font-family:Arial,sans-serif;background:#f4f4f4;margin:0;padding:20px;">'
             . '<div style="max-width:520px;margin:0 auto;background:#fff;border-radius:6px;padding:32px 36px;">'
             . '<h2 style="margin-top:0;color:#1a1a2e;">Password Reset</h2>'
             . "<p>Hello <strong>{$safeName}</strong>,</p>"
             . '<p>You requested a password reset for your ITSWEBER CMS account.</p>'
             . '<p style="margin:24px 0;">'
             . "<a href=\"{$safeUrl}\" style=\"display:inline-block;background:#4a6cf7;color:#fff;"
             . 'padding:12px 24px;border-radius:4px;text-decoration:none;font-weight:bold;">'
             . 'Reset my password</a></p>'
             . "<p style=\"color:#666;font-size:13px;\">This link is valid for <strong>{$ttl} minutes</strong>"
             . ' and can only be used once.</p>'
             . '<p style="color:#666;font-size:13px;">If you did not request a password reset, '
             . 'please ignore this e-mail. Your password will not change.</p>'
             . '<hr style="border:none;border-top:1px solid #eee;margin:24px 0;">'
             . '<p style="color:#999;font-size:12px;">ITSWEBER CMS – automated message, do not reply.</p>'
             . '</div></body></html>';
    }
}
