<?php

declare(strict_types=1);

namespace App\Controllers;

use App\Core\Auth;
use App\Core\BaseController;
use App\Core\Csrf;
use App\Core\Request;
use App\Core\Response;
use App\Core\Session;
use App\Core\Validator;
use App\Helpers\AjaxResponse;
use App\Services\AuthService;
use App\Services\EmailVerificationService;
use App\Services\PasswordPolicyService;
use App\Services\PasswordResetService;
use App\Services\UserService;
use PDO;

final class AuthController extends BaseController
{
    private const REMEMBER_COOKIE = 'ab_hub_remember';

    public function showLoginForm(Request $request): Response
    {
        return $this->view('auth/login', ['title' => 'Sign In'], useLayout: false);
    }

    public function login(Request $request): Response
    {
        $data = $this->validateOrFail($request, [
            'email' => 'required|email',
            'password' => 'required|string',
        ]);

        $auth = new AuthService($request);
        $result = $auth->attempt($data['email'], $data['password']);

        if (!$result['success']) {
            return AjaxResponse::error($result['message'], 401);
        }

        if (!empty($result['requires_2fa'])) {
            return AjaxResponse::success(['requires_2fa' => true], $result['message']);
        }

        if ($request->input('remember')) {
            $token = $auth->issueRememberToken((int) $result['user']['id'], $request->userAgent(), $request->ip());
            $this->setRememberCookie($token);
        }

        return AjaxResponse::success([
            'user' => $result['user'],
            'password_expired' => $result['password_expired'],
            'nearing_expiry' => $result['nearing_expiry'],
            'redirect' => base_url('dashboard'),
        ], $result['message']);
    }

    public function twoFactorChallenge(Request $request): Response
    {
        $data = $this->validateOrFail($request, [
            'code' => 'required|string|max:20',
        ]);

        $auth = new AuthService($request);
        $isRecovery = (bool) $request->input('is_recovery_code', false);
        $result = $auth->completeTwoFactor($data['code'], $isRecovery);

        if (!$result['success']) {
            return AjaxResponse::error($result['message'], 401);
        }

        if ($request->input('remember')) {
            $token = $auth->issueRememberToken((int) $result['user']['id'], $request->userAgent(), $request->ip());
            $this->setRememberCookie($token);
        }

        return AjaxResponse::success(['user' => $result['user'], 'redirect' => base_url('dashboard')], $result['message']);
    }

    public function logout(Request $request): Response
    {
        (new AuthService($request))->logout();

        $cookie = $request->cookie(self::REMEMBER_COOKIE);
        if (is_string($cookie)) {
            (new AuthService($request))->forgetRememberToken($cookie);
        }
        $this->clearRememberCookie();

        if ($request->isAjax() || $request->isJson()) {
            return AjaxResponse::success(['redirect' => base_url('login')], 'Logged out.');
        }

        return $this->redirect('login');
    }

    /** Attempts silent login via the remember-me cookie — called from a bootstrap check before rendering any guest-only page. */
    public function attemptRememberLogin(Request $request): bool
    {
        if (Auth::check()) {
            return true;
        }

        $cookie = $request->cookie(self::REMEMBER_COOKIE);
        if (!is_string($cookie)) {
            return false;
        }

        $success = (new AuthService($request))->attemptRememberToken($cookie);
        if (!$success) {
            $this->clearRememberCookie();
        }

        return $success;
    }

    public function showForgotPasswordForm(Request $request): Response
    {
        return $this->view('auth/forgot-password', ['title' => 'Forgot Password'], useLayout: false);
    }

    public function sendResetLink(Request $request): Response
    {
        $data = $this->validateOrFail($request, ['email' => 'required|email']);

        (new PasswordResetService())->sendResetLink($data['email'], $request->ip());

        // Always the same response, whether or not the email exists —
        // see PasswordResetService's docblock.
        return AjaxResponse::success(null, 'If that email is registered, a reset link has been sent.');
    }

    public function showResetPasswordForm(Request $request, array $params): Response
    {
        return $this->view('auth/reset-password', ['title' => 'Reset Password', 'token' => $params['token']], useLayout: false);
    }

    public function resetPassword(Request $request): Response
    {
        $data = $this->validateOrFail($request, [
            'token' => 'required|string',
            'password' => 'required|string|confirmed',
        ]);

        $result = (new PasswordResetService())->reset($data['token'], $data['password']);

        if (!$result['success']) {
            return AjaxResponse::error($result['message'], 422, $result['errors'] ?? []);
        }

        return AjaxResponse::success(['redirect' => base_url('login')], $result['message']);
    }

    public function verifyEmail(Request $request, array $params): Response
    {
        $result = (new EmailVerificationService())->verify($params['token']);

        Session::flash('_verification_result', $result);

        return $this->redirect('login');
    }

    public function acceptInvitation(Request $request, array $params): Response
    {
        $pdo = \App\Core\Database::connection();
        $hash = hash('sha256', $params['token']);

        $stmt = $pdo->prepare('SELECT * FROM user_invitations WHERE token_hash = :hash AND accepted_at IS NULL LIMIT 1');
        $stmt->execute(['hash' => $hash]);
        $invitation = $stmt->fetch(PDO::FETCH_ASSOC);

        if ($invitation === false || strtotime($invitation['expires_at']) < time()) {
            return $this->view('auth/invitation-invalid', ['title' => 'Invitation Expired'], useLayout: false);
        }

        return $this->view('auth/accept-invitation', [
            'title' => 'Set Your Password',
            'token' => $params['token'],
        ], useLayout: false);
    }

    public function completeInvitation(Request $request): Response
    {
        $data = $this->validateOrFail($request, [
            'token' => 'required|string',
            'password' => 'required|string|confirmed',
        ]);

        $pdo = \App\Core\Database::connection();
        $hash = hash('sha256', $data['token']);

        $stmt = $pdo->prepare('SELECT * FROM user_invitations WHERE token_hash = :hash AND accepted_at IS NULL LIMIT 1');
        $stmt->execute(['hash' => $hash]);
        $invitation = $stmt->fetch(PDO::FETCH_ASSOC);

        if ($invitation === false || strtotime($invitation['expires_at']) < time()) {
            return AjaxResponse::error('This invitation link is invalid or has expired.', 422);
        }

        $policy = new PasswordPolicyService();
        $errors = $policy->validateComplexity($data['password']);
        if ($errors !== []) {
            return AjaxResponse::validationError(['password' => $errors]);
        }

        $userId = (int) $invitation['user_id'];
        $policy->commit($userId, $data['password']);

        $pdo->prepare("UPDATE users SET status = 'active', email_verified_at = NOW() WHERE id = :id")
            ->execute(['id' => $userId]);
        $pdo->prepare('UPDATE user_invitations SET accepted_at = NOW() WHERE id = :id')
            ->execute(['id' => $invitation['id']]);

        return AjaxResponse::success(['redirect' => base_url('login')], 'Your password has been set. You can now sign in.');
    }

    private function setRememberCookie(string $token): void
    {
        setcookie(self::REMEMBER_COOKIE, $token, [
            'expires' => time() + (30 * 86400),
            'path' => '/',
            'secure' => (bool) config('session.cookie.secure'),
            'httponly' => true,
            'samesite' => config('session.cookie.samesite'),
        ]);
    }

    private function clearRememberCookie(): void
    {
        setcookie(self::REMEMBER_COOKIE, '', ['expires' => time() - 3600, 'path' => '/']);
    }
}
