<?php
declare(strict_types=1);

namespace App\Controllers;

use App\Core\{Auth, Csrf, Database, Response, Session};
use App\Models\User;
use Throwable;
use function App\Core\{__, url};

class AuthController extends BaseController
{
    private const LOGIN_LIMIT = 5;
    private const LOGIN_WINDOW_MINUTES = 15;
    private const RESET_TOKEN_MINUTES = 60;

    public function login(): string
    {
        return $this->view('main', 'auth/login', [
            'title' => __('login'),
            'rememberedIdentity' => $_COOKIE['zalorox_login_hint'] ?? '',
        ]);
    }

    public function register(): string
    {
        return $this->view('main', 'auth/register', ['title' => __('register')]);
    }

    public function forgot(): string
    {
        return $this->view('main', 'auth/forgot-password', ['title' => __('forgot_password')]);
    }

    public function reset(): string
    {
        $token = trim((string) ($_GET['token'] ?? ''));

        return $this->view('main', 'auth/reset-password', [
            'title' => __('reset_password'),
            'token' => $token,
            'tokenIsValid' => $token !== '' && $this->validResetToken($token) !== null,
        ]);
    }

    public function loginPost(): Response
    {
        if (!Csrf::verify()) {
            return $this->backWithErrors('/giris-yap', [__('csrf_failed')]);
        }

        $identifier = trim((string) ($_POST['identity'] ?? $_POST['email'] ?? ''));
        $password = (string) ($_POST['password'] ?? '');
        $remember = !empty($_POST['remember']);
        $errors = [];

        if ($identifier === '') {
            $errors[] = __('auth_identifier_required');
        }

        if ($password === '') {
            $errors[] = __('password_required');
        }

        $rateKey = $this->rateKey('login', $identifier);
        if (!$errors && $this->isRateLimited($rateKey)) {
            $errors[] = __('rate_limited');
        }

        if ($errors) {
            return $this->backWithErrors('/giris-yap', $errors, ['identity' => $identifier]);
        }

        try {
            if (!Auth::attempt($identifier, $password, $this->config)) {
                $this->hitRateLimit($rateKey);

                return $this->backWithErrors('/giris-yap', [__('login_failed')], ['identity' => $identifier]);
            }

            $this->clearRateLimit($rateKey);
            $this->persistSessionCookie($remember);
            $this->rememberIdentity($remember, $identifier);
            $user = Auth::user() ?? [];
            $role = (string) ($user['role'] ?? 'user');

            Session::flash('toast', ['type' => 'success', 'message' => __('login_success')]);

            return Response::redirect(url(match ($role) {
                'admin' => '/zpanel',
                'creator', 'moderator' => '/studyo',
                default => '/',
            }));
        } catch (Throwable $exception) {
            $this->logException($exception, 'auth.login');

            return $this->backWithErrors('/giris-yap', [__('auth_system_error')], ['identity' => $identifier]);
        }
    }

    public function registerPost(): Response
    {
        if (!Csrf::verify()) {
            return $this->backWithErrors('/kayit', [__('csrf_failed')]);
        }

        $supportedLanguages = $this->supportedLanguages();
        $old = [
            'nickname' => trim((string) ($_POST['nickname'] ?? '')),
            'username' => strtolower(trim((string) ($_POST['username'] ?? ''))),
            'email' => strtolower(trim((string) ($_POST['email'] ?? ''))),
            'language' => trim((string) ($_POST['language'] ?? '')),
        ];
        $password = (string) ($_POST['password'] ?? '');
        $passwordConfirmation = (string) ($_POST['password_confirmation'] ?? '');
        $language = in_array($old['language'], $supportedLanguages, true)
            ? $old['language']
            : (string) ($this->config['DEFAULT_LANGUAGE'] ?? 'tr');
        $errors = $this->validateRegistration($old, $password, $passwordConfirmation);

        try {
            $users = new User();

            if (!$errors) {
                if ($users->usernameExists($old['username'])) {
                    $errors[] = __('username_taken');
                }

                if ($users->emailExists($old['email'])) {
                    $errors[] = __('email_taken');
                }
            }

            if ($errors) {
                return $this->backWithErrors('/kayit', $errors, $old);
            }

            $users->createWithProfile([
                'username' => $old['username'],
                'email' => $old['email'],
                'password_hash' => password_hash($password, PASSWORD_DEFAULT),
                'nickname' => $old['nickname'],
                'role' => 'user',
                'status' => 'active',
                'language' => $language,
            ]);

            Session::flash('toast', ['type' => 'success', 'message' => __('register_success')]);

            return Response::redirect(url('/giris-yap'));
        } catch (Throwable $exception) {
            $this->logException($exception, 'auth.register');

            return $this->backWithErrors('/kayit', [__('auth_system_error')], $old);
        }
    }

    public function forgotPost(): Response
    {
        if (!Csrf::verify()) {
            return $this->backWithErrors('/sifremi-unuttum', [__('csrf_failed')]);
        }

        $email = strtolower(trim((string) ($_POST['email'] ?? '')));
        if (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
            return $this->backWithErrors('/sifremi-unuttum', [__('email_invalid')], ['email' => $email]);
        }

        try {
            $user = (new User())->findByEmail($email);
            if ($user && ($user['status'] ?? '') === 'active') {
                $token = bin2hex(random_bytes(32));
                $this->createPasswordReset((int) $user['id'], $email, $token);
                $resetLink = url('/sifre-sifirla?token=' . $token);
                if (!empty($this->config['FAKE_EMAIL_MODE'])) {
                    $this->logFakeMail($email, 'Zalorox sifre sifirlama', $resetLink);
                }
                Session::flash('reset_link', $resetLink);
            }

            Session::flash('toast', ['type' => 'success', 'message' => __('reset_request_success')]);

            return Response::redirect(url('/sifremi-unuttum'));
        } catch (Throwable $exception) {
            $this->logException($exception, 'auth.forgot');

            return $this->backWithErrors('/sifremi-unuttum', [__('auth_system_error')], ['email' => $email]);
        }
    }

    public function resetPost(): Response
    {
        if (!Csrf::verify()) {
            return $this->backWithErrors('/sifre-sifirla', [__('csrf_failed')]);
        }

        $token = trim((string) ($_POST['token'] ?? ''));
        $password = (string) ($_POST['password'] ?? '');
        $passwordConfirmation = (string) ($_POST['password_confirmation'] ?? '');
        $errors = [];

        if ($token === '' || $this->validResetToken($token) === null) {
            $errors[] = __('reset_token_invalid');
        }

        if (strlen($password) < 8) {
            $errors[] = __('password_min');
        }

        if ($password !== $passwordConfirmation) {
            $errors[] = __('password_confirmation_mismatch');
        }

        if ($errors) {
            return $this->backWithErrors('/sifre-sifirla' . ($token !== '' ? '?token=' . urlencode($token) : ''), $errors);
        }

        try {
            $reset = $this->validResetToken($token);
            if (!$reset) {
                return $this->backWithErrors('/sifre-sifirla', [__('reset_token_invalid')]);
            }

            $pdo = Database::connection($this->config);
            $pdo->beginTransaction();
            (new User($pdo))->updatePassword((int) $reset['user_id'], password_hash($password, PASSWORD_DEFAULT));

            $statement = $pdo->prepare('UPDATE password_resets SET used_at = NOW() WHERE id = :id');
            $statement->execute(['id' => (int) $reset['id']]);
            $pdo->commit();

            Session::flash('toast', ['type' => 'success', 'message' => __('password_reset_success')]);

            return Response::redirect(url('/giris-yap'));
        } catch (Throwable $exception) {
            if (isset($pdo) && $pdo->inTransaction()) {
                $pdo->rollBack();
            }

            $this->logException($exception, 'auth.reset');

            return $this->backWithErrors('/sifre-sifirla', [__('auth_system_error')]);
        }
    }

    public function logout(): Response
    {
        Auth::logout();

        return Response::redirect(url('/'));
    }

    private function validateRegistration(array $input, string $password, string $passwordConfirmation): array
    {
        $errors = [];

        if ($input['nickname'] === '' || strlen($input['nickname']) < 2 || strlen($input['nickname']) > 120) {
            $errors[] = __('nickname_invalid');
        }

        if (!preg_match('/^[a-z0-9_\.]{3,30}$/', $input['username'])) {
            $errors[] = __('username_invalid');
        }

        if (!filter_var($input['email'], FILTER_VALIDATE_EMAIL)) {
            $errors[] = __('email_invalid');
        }

        if (strlen($password) < 8) {
            $errors[] = __('password_min');
        }

        if ($password !== $passwordConfirmation) {
            $errors[] = __('password_confirmation_mismatch');
        }

        if (empty($_POST['privacy_accepted'])) {
            $errors[] = __('privacy_required');
        }

        return $errors;
    }

    private function backWithErrors(string $path, array $errors, array $old = []): Response
    {
        Session::flash('auth_errors', $errors);
        Session::flash('old_input', $old);
        Session::flash('toast', ['type' => 'error', 'message' => implode(' ', $errors)]);

        return Response::redirect(url($path));
    }

    private function supportedLanguages(): array
    {
        return $this->config['supported_languages'] ?? ['tr', 'en', 'de', 'fr'];
    }

    private function rateKey(string $scope, string $identifier): string
    {
        $ip = $_SERVER['REMOTE_ADDR'] ?? 'unknown';

        return $scope . ':' . sha1($ip . '|' . strtolower(trim($identifier)));
    }

    private function isRateLimited(string $key): bool
    {
        try {
            $statement = Database::connection($this->config)->prepare('SELECT attempts, expires_at FROM rate_limits WHERE key_name = :key_name LIMIT 1');
            $statement->execute(['key_name' => $key]);
            $row = $statement->fetch();

            return $row
                && strtotime((string) $row['expires_at']) > time()
                && (int) $row['attempts'] >= self::LOGIN_LIMIT;
        } catch (Throwable) {
            return false;
        }
    }

    private function hitRateLimit(string $key): void
    {
        try {
            $statement = Database::connection($this->config)->prepare(
                'INSERT INTO rate_limits (key_name, attempts, expires_at, created_at)
                VALUES (:key_name, 1, DATE_ADD(NOW(), INTERVAL ' . self::LOGIN_WINDOW_MINUTES . ' MINUTE), NOW())
                ON DUPLICATE KEY UPDATE
                    attempts = IF(expires_at < NOW(), 1, attempts + 1),
                    expires_at = IF(expires_at < NOW(), VALUES(expires_at), expires_at),
                    created_at = IF(expires_at < NOW(), VALUES(created_at), created_at)'
            );
            $statement->execute(['key_name' => $key]);
        } catch (Throwable) {
        }
    }

    private function clearRateLimit(string $key): void
    {
        try {
            $statement = Database::connection($this->config)->prepare('DELETE FROM rate_limits WHERE key_name = :key_name');
            $statement->execute(['key_name' => $key]);
        } catch (Throwable) {
        }
    }

    private function createPasswordReset(int $userId, string $email, string $token): void
    {
        $pdo = Database::connection($this->config);
        $pdo->beginTransaction();

        try {
            $invalidate = $pdo->prepare('UPDATE password_resets SET used_at = NOW() WHERE user_id = :user_id AND used_at IS NULL');
            $invalidate->execute(['user_id' => $userId]);

            $statement = $pdo->prepare(
                'INSERT INTO password_resets (user_id, email, token_hash, expires_at, ip_address)
                VALUES (:user_id, :email, :token_hash, DATE_ADD(NOW(), INTERVAL ' . self::RESET_TOKEN_MINUTES . ' MINUTE), :ip_address)'
            );
            $statement->execute([
                'user_id' => $userId,
                'email' => $email,
                'token_hash' => hash('sha256', $token),
                'ip_address' => $_SERVER['REMOTE_ADDR'] ?? null,
            ]);
            $pdo->commit();
        } catch (Throwable $exception) {
            if ($pdo->inTransaction()) {
                $pdo->rollBack();
            }

            throw $exception;
        }
    }

    private function logFakeMail(string $to, string $subject, string $body): void
    {
        $logDir = $this->config['paths']['logs'] ?? BASE_PATH . '/storage/logs';
        if (!is_dir($logDir)) {
            @mkdir($logDir, 0775, true);
        }

        if (!is_dir($logDir)) {
            return;
        }

        $entry = '[' . date('c') . '] To: ' . $to . ' | Subject: ' . $subject . PHP_EOL . $body . PHP_EOL . PHP_EOL;
        error_log($entry, 3, $logDir . '/mail.log');
    }

    private function validResetToken(string $token): ?array
    {
        if (!preg_match('/^[a-f0-9]{64}$/i', $token)) {
            return null;
        }

        try {
            $statement = Database::connection($this->config)->prepare(
                "SELECT pr.*
                FROM password_resets pr
                INNER JOIN users u ON u.id = pr.user_id
                WHERE pr.token_hash = :token_hash
                    AND pr.used_at IS NULL
                    AND pr.expires_at > NOW()
                    AND u.status = 'active'
                LIMIT 1"
            );
            $statement->execute(['token_hash' => hash('sha256', $token)]);
            $row = $statement->fetch();

            return $row ?: null;
        } catch (Throwable) {
            return null;
        }
    }

    private function persistSessionCookie(bool $remember): void
    {
        if (!$remember || session_status() !== PHP_SESSION_ACTIVE) {
            return;
        }

        $params = session_get_cookie_params();
        $secure = (!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off');
        setcookie(session_name(), session_id(), [
            'expires' => time() + 60 * 60 * 24 * 30,
            'path' => $params['path'] ?: '/',
            'domain' => $params['domain'] ?? '',
            'secure' => $secure,
            'httponly' => true,
            'samesite' => $params['samesite'] ?? 'Lax',
        ]);
    }

    private function rememberIdentity(bool $remember, string $identifier): void
    {
        $secure = (!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off');
        setcookie('zalorox_login_hint', $remember ? $identifier : '', [
            'expires' => $remember ? time() + 60 * 60 * 24 * 30 : time() - 3600,
            'path' => '/',
            'secure' => $secure,
            'httponly' => false,
            'samesite' => 'Lax',
        ]);
    }
}
