<?php

declare(strict_types=1);

namespace App\Modules\Auth;

use App\Core\BaseController;
use App\Core\Logger;
use App\Core\Request;
use App\Core\Session;

/**
 * AuthController — Authentication HTTP Handler
 *
 * Thin controller (DBP v1.1 §1.5): receives the request, validates input,
 * delegates all business logic to AuthService, and returns the response.
 *
 * Routes handled (AUTH-001):
 *   GET  /login              → showLogin()             — render the unified login form
 *   POST /login              → processLogin()          — validate input, call AuthService, redirect
 *
 * Routes handled (AUTH-002):
 *   POST /logout             → logout()                — destroy session, redirect to /login
 *
 * Routes handled (AUTH-003):
 *   GET  /forgot-password    → showForgotPassword()    — render the forgot-password form
 *   POST /forgot-password    → processForgotPassword() — trigger token generation, always redirect
 *   GET  /reset-password/{token} → showResetPassword()    — validate token, render reset form
 *   POST /reset-password/{token} → processResetPassword() — validate, update password, redirect
 *
 * Architecture rules:
 *   - No SQL in this class.
 *   - No business logic in this class.
 *   - No password handling in this class.
 *   - All methods must remain under 20 lines (DBP v1.1 §11.4).
 *
 * Security:
 *   - POST /login  is protected by CsrfMiddleware (registered in routes.php).
 *   - POST /logout is protected by CsrfMiddleware (registered in routes.php).
 *   - POST /forgot-password is protected by CsrfMiddleware (registered in routes.php).
 *   - POST /reset-password/{token} is protected by CsrfMiddleware (registered in routes.php).
 *   - GET logout is NOT permitted — the route only accepts POST.
 *   - Authenticated users who reach GET /login are redirected to their dashboard.
 *   - All output in views uses escape_html() — no raw echo.
 *
 * Layout: 'public' — all auth pages use the public layout (no navigation chrome).
 *
 * @package Inveetaire
 * @ref     SPRINT2_PLANNING.md §5, DBP v1.1 §1.5, AUTH-001, AUTH-002, AUTH-003
 */
class AuthController extends BaseController
{
    /**
     * Use the public layout — no navigation chrome for the login page.
     * @var string
     */
    protected string $layout = 'public';

    /** @var AuthService */
    private AuthService $service;

    public function __construct()
    {
        $this->service = new AuthService();
    }

    // ─────────────────────────────────────────────────────────────────────────
    // GET /login — Show Login Form
    // ─────────────────────────────────────────────────────────────────────────

    /**
     * Display the unified login form.
     *
     * Redirects already-authenticated users to their portal dashboard
     * to avoid double-login state.
     *
     * @param Request $request The current HTTP request.
     * @return void
     */
    public function showLogin(Request $request): void
    {
        // Redirect authenticated users away from the login page.
        if (Session::has('auth_user_id')) {
            $role = (string) Session::get('role_snapshot', '');
            $this->redirect($this->portalPath($role));
            return;
        }

        $this->render('auth/login', ['title' => 'Sign In']);
    }

    // ─────────────────────────────────────────────────────────────────────────
    // POST /login — Process Login
    // ─────────────────────────────────────────────────────────────────────────

    /**
     * Process login form submission.
     *
     * Input validation (controller-level — non-empty only):
     *   identifier — email or username — must not be empty
     *   password   — must not be empty
     *
     * All credential verification is delegated to AuthService::attemptLogin().
     * On success: redirect to the role-specific portal dashboard.
     * On failure: re-render the login form with a flash error message.
     *
     * @param Request $request The current HTTP request.
     * @return void
     */
    public function processLogin(Request $request): void
    {
        $identifier = trim((string) $request->post('identifier', ''));
        $password   = (string) $request->post('password', '');
        $clientIp   = (string) $request->getIp();

        if ($identifier === '' || $password === '') {
            $this->flash('error', 'Please enter your email (or username) and password.');
            $this->redirect('/login');
            return;
        }

        $result = $this->service->attemptLogin($identifier, $password, $clientIp);

        if (!$result['success']) {
            Logger::warning('Login attempt failed — redirecting back to form', [
                'ip' => $clientIp,
            ]);
            $this->flash('error', $result['error']);
            $this->redirect('/login');
            return;
        }

        $this->redirect($result['redirect']);
    }

    // ─────────────────────────────────────────────────────────────────────────
    // GET /forgot-password — Show Forgot Password Form (AUTH-003)
    // ─────────────────────────────────────────────────────────────────────────

    /**
     * Display the forgot-password form.
     *
     * Authenticated users are redirected to their portal — they don't need
     * to reset a password they are already using.
     *
     * @param Request $request The current HTTP request.
     * @return void
     */
    public function showForgotPassword(Request $request): void
    {
        if (Session::has('auth_user_id')) {
            $role = (string) Session::get('role_snapshot', '');
            $this->redirect($this->portalPath($role));
            return;
        }

        $this->render('auth/forgot_password', ['title' => 'Forgot Password']);
    }

    // ─────────────────────────────────────────────────────────────────────────
    // POST /forgot-password — Process Forgot Password (AUTH-003)
    // ─────────────────────────────────────────────────────────────────────────

    /**
     * Process the forgot-password form submission.
     *
     * Delegates entirely to AuthService::requestPasswordReset().
     * Always redirects back to GET /forgot-password with a flash message
     * (including when the email is not found — prevents user enumeration).
     *
     * @param Request $request The current HTTP request.
     * @return void
     */
    public function processForgotPassword(Request $request): void
    {
        $email    = trim((string) $request->post('email', ''));
        $clientIp = (string) $request->getIp();

        $result = $this->service->requestPasswordReset($email, $clientIp);

        $type = $result['success'] ? 'success' : 'error';
        $this->flash($type, $result['message']);
        $this->redirect('/forgot-password');
    }

    // ─────────────────────────────────────────────────────────────────────────
    // GET /reset-password/{token} — Show Reset Password Form (AUTH-003)
    // ─────────────────────────────────────────────────────────────────────────

    /**
     * Render the reset-password form for a given token.
     *
     * Pre-validates the token before rendering. If the token is invalid or
     * expired, redirects to GET /forgot-password with an error flash message.
     * This prevents the form from rendering in a broken state.
     *
     * @param Request $request The current HTTP request.
     * @return void
     */
    public function showResetPassword(Request $request): void
    {
        $token   = (string) $request->param('token', '');
        $payload = $this->service->loadResetToken($token);

        if ($payload === null) {
            $this->flash('error', 'This reset link is invalid or has expired. Please request a new one.');
            $this->redirect('/forgot-password');
            return;
        }

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

    // ─────────────────────────────────────────────────────────────────────────
    // POST /reset-password/{token} — Process Reset Password (AUTH-003)
    // ─────────────────────────────────────────────────────────────────────────

    /**
     * Process the reset-password form submission.
     *
     * Input validation (controller-level — non-empty only):
     *   token            — must not be empty (from URL param)
     *   password         — must not be empty
     *   password_confirm — must not be empty
     *
     * All token validation, password hashing, and DB update are delegated
     * to AuthService::resetPassword().
     *
     * On success: redirects to /login with a success flash message.
     * On failure: redirects back to GET /reset-password/{token} with an error flash.
     *
     * @param Request $request The current HTTP request.
     * @return void
     */
    public function processResetPassword(Request $request): void
    {
        $token           = (string) $request->param('token', '');
        $password        = (string) $request->post('password', '');
        $passwordConfirm = (string) $request->post('password_confirm', '');
        $clientIp        = (string) $request->getIp();

        if ($token === '' || $password === '' || $passwordConfirm === '') {
            $this->flash('error', 'All fields are required.');
            $this->redirect('/reset-password/' . rawurlencode($token));
            return;
        }

        $result = $this->service->resetPassword($token, $password, $passwordConfirm, $clientIp);

        if (!$result['success']) {
            $this->flash('error', $result['message']);
            $this->redirect('/reset-password/' . rawurlencode($token));
            return;
        }

        $this->flash('success', $result['message']);
        $this->redirect('/login');
    }

    // ─────────────────────────────────────────────────────────────────────────
    // POST /logout — Destroy Session and Redirect (AUTH-002)
    // ─────────────────────────────────────────────────────────────────────────

    /**
     * Destroy the authenticated session and redirect to the login page.
     *
     * Only POST requests are accepted. The route is registered with
     * CsrfMiddleware — any request without a valid CSRF token is rejected
     * by the middleware before this method executes.
     *
     * No-cache headers are sent to prevent the browser from serving a
     * stale authenticated page when the user presses Back after logout.
     *
     * All session destruction logic is delegated to AuthService::performLogout().
     *
     * @param Request $request The current HTTP request.
     * @return void
     */
    public function logout(Request $request): void
    {
        // Emit no-cache headers before any output — prevents Back-button replay.
        header('Cache-Control: no-store, no-cache, must-revalidate, max-age=0');
        header('Pragma: no-cache');
        header('Expires: Sat, 01 Jan 2000 00:00:00 GMT');

        $this->service->performLogout((string) $request->getIp());

        $this->redirect('/');
    }

    // ─────────────────────────────────────────────────────────────────────────
    // NEW PORTAL ENTRY POINTS — show & process actions (ALPHA STABILIZATION-002)
    // ─────────────────────────────────────────────────────────────────────────

    /**
     * Display the Operator Platform login form at /admin.
     */
    public function showAdminLogin(Request $request): void
    {
        if (Session::has('auth_user_id')) {
            $role = (string) Session::get('role_snapshot', '');
            $this->redirect($this->portalPath($role));
            return;
        }

        $this->render('auth/login', [
            'title'      => 'Platform Sign In',
            'action'     => '/admin',
            'heading'    => 'Platform Admin',
            'subheading' => 'Sign in to the Inveetaire operator platform'
        ]);
    }

    /**
     * Process Operator Platform login at /admin.
     */
    public function processAdminLogin(Request $request): void
    {
        $identifier = trim((string) $request->post('identifier', ''));
        $password   = (string) $request->post('password', '');
        $clientIp   = (string) $request->getIp();

        if ($identifier === '' || $password === '') {
            $this->flash('error', 'Please enter your email (or username) and password.');
            $this->redirect('/admin');
            return;
        }

        $result = $this->service->attemptLogin($identifier, $password, $clientIp);

        if (!$result['success']) {
            $this->flash('error', $result['error']);
            $this->redirect('/admin');
            return;
        }

        $this->redirect($result['redirect']);
    }

    /**
     * Display the Workspace couple login form at /couple.
     */
    public function showCoupleLogin(Request $request): void
    {
        if (Session::has('auth_user_id')) {
            $role = (string) Session::get('role_snapshot', '');
            $this->redirect($this->portalPath($role));
            return;
        }

        $this->render('auth/login', [
            'title'      => 'Workspace Sign In',
            'action'     => '/couple',
            'heading'    => 'Welcome back',
            'subheading' => 'Sign in to your wedding workspace'
        ]);
    }

    /**
     * Process Workspace couple login at /couple.
     */
    public function processCoupleLogin(Request $request): void
    {
        $identifier = trim((string) $request->post('identifier', ''));
        $password   = (string) $request->post('password', '');
        $clientIp   = (string) $request->getIp();

        if ($identifier === '' || $password === '') {
            $this->flash('error', 'Please enter your email (or username) and password.');
            $this->redirect('/couple');
            return;
        }

        $result = $this->service->attemptLogin($identifier, $password, $clientIp);

        if (!$result['success']) {
            $this->flash('error', $result['error']);
            $this->redirect('/couple');
            return;
        }

        $this->redirect($result['redirect']);
    }

    /**
     * Redirect legacy /login requests (302) to /couple.
     */
    public function legacyLoginRedirect(Request $request): void
    {
        $this->redirect('/couple');
    }

    // ─────────────────────────────────────────────────────────────────────────
    // PRIVATE — Portal Path Helper
    // ─────────────────────────────────────────────────────────────────────────

    /**
     * Resolve the portal dashboard path for a given role.
     *
     * Used to redirect already-authenticated users away from GET /login.
     *
     * @param string $role  Role snapshot value from session.
     * @return string       Absolute URI path.
     */
    private function portalPath(string $role): string
    {
        $map = [
            'super_admin' => '/app/admin/dashboard',
            'couple'      => '/app/couple/dashboard',
            'crew'        => '/app/crew/dashboard',
        ];

        return $map[$role] ?? '/couple';
    }
}
