<?php

declare(strict_types=1);

namespace FatBottom\Controllers;

use FatBottom\Core\Auth;
use FatBottom\Core\Config;
use FatBottom\Core\Database;
use FatBottom\Core\Http;
use FatBottom\Core\Security;
use FatBottom\Core\View;
use FatBottom\Services\CartService;
use FatBottom\Services\TwoFactorService;

final class AuthController extends BaseController
{
    public function __construct(
        Config $config,
        Auth $auth,
        CartService $cart,
        private readonly Database $db,
        private readonly TwoFactorService $twoFactor
    ) {
        parent::__construct($config, $auth, $cart);
    }

    public function loginForm(): void
    {
        if ($this->auth->check()) {
            Http::redirect('/account');
        }
        View::render('auth/login', $this->shared([
            'title' => 'Sign In',
            'captcha' => Security::newCaptcha(),
        ]));
    }

    public function login(): void
    {
        Security::verifyCsrf();
        $input = $_POST;
        $email = strtolower(Security::clean((string) ($input['email'] ?? '')));
        if (!Security::verifyCaptcha($input)) {
            Http::old(['email' => $email]);
            Http::flash('error', 'Please complete the anti-spam check.');
            Http::redirect('/login');
        }

        $user = $this->db->fetch('SELECT * FROM users WHERE email = ? AND active = 1', [$email]);
        if ($user === null || !password_verify((string) ($input['password'] ?? ''), (string) $user['password_hash'])) {
            usleep(350000);
            Http::old(['email' => $email]);
            Http::flash('error', 'The email address or password was not recognized.');
            Http::redirect('/login');
        }

        unset($_SESSION['pending_2fa_user_id'], $_SESSION['pending_verification_purpose']);
        if ((bool) ($user['two_factor_enabled'] ?? false)) {
            $_SESSION['pending_2fa_user_id'] = (int) $user['id'];
            try {
                $developmentCode = $this->twoFactor->issue($user, 'login');
            } catch (\Throwable) {
                unset($_SESSION['pending_2fa_user_id']);
                Http::flash('error', 'We could not send your sign-in code. Please try again or ask an administrator to disable email 2FA for your account.');
                Http::redirect('/login');
            }
            if ($developmentCode !== null) {
                Http::flash('info', 'Development mail mode: your verification code is ' . $developmentCode . '.');
            } else {
                Http::flash('success', 'We emailed you a six-digit verification code.');
            }
            Http::redirect('/verify');
        }

        $this->auth->login($user);
        Http::flash('success', 'Welcome, ' . $user['full_name'] . '.');
        Http::redirect(in_array($user['role'], ['admin', 'manager', 'staff'], true) ? '/admin' : '/account');
    }

    public function registerForm(): void
    {
        if ($this->auth->check()) {
            Http::redirect('/account');
        }
        View::render('auth/register', $this->shared([
            'title' => 'Create Account',
            'captcha' => Security::newCaptcha(),
        ]));
    }

    public function register(): void
    {
        Security::verifyCsrf();
        $input = $_POST;
        $clean = [
            'email' => strtolower(Security::clean((string) ($input['email'] ?? ''))),
            'full_name' => Security::clean((string) ($input['full_name'] ?? '')),
            'phone' => Security::clean((string) ($input['phone'] ?? '')),
            'street_address' => Security::clean((string) ($input['street_address'] ?? '')),
            'city' => Security::clean((string) ($input['city'] ?? '')),
            'state' => strtoupper(substr(Security::clean((string) ($input['state'] ?? 'WV')), 0, 2)),
            'postal_code' => Security::clean((string) ($input['postal_code'] ?? '')),
        ];

        if (!Security::verifyCaptcha($input)) {
            Http::old($clean);
            Http::flash('error', 'Please complete the anti-spam check.');
            Http::redirect('/register');
        }
        if (
            !filter_var($clean['email'], FILTER_VALIDATE_EMAIL)
            || $clean['full_name'] === ''
            || $clean['phone'] === ''
            || $clean['street_address'] === ''
            || $clean['city'] === ''
            || $clean['state'] === ''
            || $clean['postal_code'] === ''
            || strlen((string) ($input['password'] ?? '')) < 10
        ) {
            Http::old($clean);
            Http::flash('error', 'Complete your contact and address details, enter a valid email, and use a password of at least 10 characters.');
            Http::redirect('/register');
        }
        if (($input['password'] ?? '') !== ($input['password_confirmation'] ?? '')) {
            Http::old($clean);
            Http::flash('error', 'The password confirmation does not match.');
            Http::redirect('/register');
        }
        if ($this->db->fetch('SELECT id FROM users WHERE email = ?', [$clean['email']])) {
            Http::old($clean);
            Http::flash('error', 'An account already exists for that email address.');
            Http::redirect('/login');
        }

        $this->db->execute(
            'INSERT INTO users
                (email, password_hash, full_name, phone, street_address, city, state, postal_code,
                 newsletter_opt_in, two_factor_enabled)
             VALUES (?, ?, ?, ?, ?, ?, ?, ?, 1, 0)',
            [
                $clean['email'],
                password_hash((string) $input['password'], PASSWORD_DEFAULT),
                $clean['full_name'],
                $clean['phone'],
                $clean['street_address'],
                $clean['city'],
                $clean['state'],
                $clean['postal_code'],
            ]
        );
        $user = $this->db->fetch('SELECT * FROM users WHERE email = ?', [$clean['email']]);
        if ($user === null) {
            Http::flash('error', 'Your account could not be loaded after registration.');
            Http::redirect('/login');
        }
        $this->db->execute(
            'UPDATE customers SET user_id = ?, updated_at = CURRENT_TIMESTAMP WHERE email = ?',
            [(int) $user['id'], $clean['email']]
        );
        $this->db->execute(
            'UPDATE orders SET user_id = ? WHERE user_id IS NULL AND customer_email = ?',
            [(int) $user['id'], $clean['email']]
        );
        $this->auth->login($user);
        Http::flash('success', 'Welcome, ' . $user['full_name'] . '. Your account is ready.');
        Http::redirect('/account');
    }

    public function verifyForm(): void
    {
        if (empty($_SESSION['pending_2fa_user_id'])) {
            Http::redirect('/login');
        }
        View::render('auth/verify', $this->shared([
            'title' => 'Verify Your Email',
        ]));
    }

    public function verify(): void
    {
        Security::verifyCsrf();
        $userId = (int) ($_SESSION['pending_2fa_user_id'] ?? 0);
        $purpose = (string) ($_SESSION['pending_verification_purpose'] ?? 'login');
        if (!$this->twoFactor->verify($userId, Security::clean((string) ($_POST['code'] ?? '')), $purpose)) {
            Http::flash('error', 'That code is invalid or has expired.');
            Http::redirect('/verify');
        }

        $user = $this->db->fetch('SELECT * FROM users WHERE id = ?', [$userId]);
        if ($user === null) {
            Http::redirect('/login');
        }
        if ($purpose === 'register' || empty($user['email_verified_at'])) {
            $this->db->execute('UPDATE users SET email_verified_at = CURRENT_TIMESTAMP WHERE id = ?', [$userId]);
        }
        unset($_SESSION['pending_verification_purpose']);
        $this->auth->login($user);
        Http::flash('success', 'Welcome, ' . $user['full_name'] . '.');
        Http::redirect(in_array($user['role'], ['admin', 'manager', 'staff'], true) ? '/admin' : '/account');
    }

    public function logout(): void
    {
        Security::verifyCsrf();
        $this->auth->logout();
        Http::flash('success', 'You have been signed out.');
        Http::redirect('/');
    }

    public function account(): void
    {
        $user = $this->auth->requireLogin();
        $orders = $this->db->fetchAll(
            'SELECT * FROM orders WHERE user_id = ? ORDER BY placed_at DESC LIMIT 25',
            [(int) $user['id']]
        );
        View::render('account/index', $this->shared([
            'title' => 'My Account',
            'orders' => $orders,
        ]));
    }

    public function updateAccount(): void
    {
        Security::verifyCsrf();
        $user = $this->auth->requireLogin();
        $newsletter = isset($_POST['newsletter_opt_in']) ? 1 : 0;
        $twoFactorEnabled = isset($_POST['two_factor_enabled']) ? 1 : 0;
        $newPassword = (string) ($_POST['new_password'] ?? '');
        if (
            $newPassword !== ''
            && (
                !password_verify((string) ($_POST['current_password'] ?? ''), (string) $user['password_hash'])
                || strlen($newPassword) < 10
                || $newPassword !== (string) ($_POST['new_password_confirmation'] ?? '')
            )
        ) {
            Http::flash('error', 'Password was not changed. Check your current password and use a matching new password of at least 10 characters.');
            Http::redirect('/account');
        }

        $this->db->execute(
            'UPDATE users SET full_name = ?, phone = ?, street_address = ?, city = ?, state = ?,
                postal_code = ?, newsletter_opt_in = ?, two_factor_enabled = ?,
                updated_at = CURRENT_TIMESTAMP WHERE id = ?',
            [
                Security::clean((string) ($_POST['full_name'] ?? '')),
                Security::clean((string) ($_POST['phone'] ?? '')),
                Security::clean((string) ($_POST['street_address'] ?? '')),
                Security::clean((string) ($_POST['city'] ?? '')),
                strtoupper(substr(Security::clean((string) ($_POST['state'] ?? 'WV')), 0, 2)),
                Security::clean((string) ($_POST['postal_code'] ?? '')),
                $newsletter,
                $twoFactorEnabled,
                (int) $user['id'],
            ]
        );

        if ($newPassword !== '') {
            $this->db->execute(
                'UPDATE users SET password_hash = ?, updated_at = CURRENT_TIMESTAMP WHERE id = ?',
                [password_hash($newPassword, PASSWORD_DEFAULT), (int) $user['id']]
            );
        }

        Http::flash('success', 'Your account has been updated.');
        Http::redirect('/account');
    }

    public function unsubscribe(): void
    {
        $userId = (int) ($_GET['user'] ?? 0);
        $signature = (string) ($_GET['signature'] ?? '');
        $expected = hash_hmac('sha256', (string) $userId, $this->newsletterKey());
        if ($userId > 0 && hash_equals($expected, $signature)) {
            $this->db->execute('UPDATE users SET newsletter_opt_in = 0 WHERE id = ?', [$userId]);
            Http::flash('success', 'You have been unsubscribed from email news.');
        } else {
            Http::flash('error', 'That unsubscribe link is invalid.');
        }
        Http::redirect('/');
    }

    private function newsletterKey(): string
    {
        return hash('sha256', (string) $this->config->get('app.key') . '|newsletter');
    }
}
