<?php

namespace Core\App\Services\Auth;

class LoginThrottleService
{
    private const WINDOW_SECONDS = 900;
    private const MAX_ATTEMPTS_IP = 10;
    private const MAX_ATTEMPTS_USER = 5;

    private function dbOrNull(): ?\PDO
    {
        if (!function_exists('db')) {
            return null;
        }

        try {
            return db();
        } catch (\Throwable $e) {
            return null;
        }
    }

    public function assertAllowed(string $username, string $ip): void
    {
        if ($this->tooManyAttempts($this->ipKey($ip), self::MAX_ATTEMPTS_IP) || $this->tooManyAttempts($this->userKey($username), self::MAX_ATTEMPTS_USER)) {
            http_response_code(429);
            $_SESSION['login_error'] = __('admin.login.throttled', 'Too many login attempts. Please try again later.');
            \Core\System\Response::redirect('/admin/login');
        }
    }

    public function recordFailure(string $username, string $ip): void
    {
        $this->hit($this->ipKey($ip));
        $this->hit($this->userKey($username));
    }

    public function clear(string $username, string $ip): void
    {
        $db = $this->dbOrNull();
        if (!$db) {
            return;
        }

        $stmt = $db->prepare('DELETE FROM security_rate_limits WHERE ip IN (?, ?)');
        $stmt->execute([$this->ipKey($ip), $this->userKey($username)]);
    }

    private function tooManyAttempts(string $key, int $limit): bool
    {
        $db = $this->dbOrNull();
        if (!$db) {
            return false;
        }

        $stmt = $db->prepare('SELECT requests, window_start FROM security_rate_limits WHERE ip = ? LIMIT 1');
        $stmt->execute([$key]);
        $row = $stmt->fetch(\PDO::FETCH_ASSOC);

        if (!$row) {
            return false;
        }

        if ((time() - (int)$row['window_start']) > self::WINDOW_SECONDS) {
            $this->reset($key);
            return false;
        }

        return (int)$row['requests'] >= $limit;
    }

    private function hit(string $key): void
    {
        $db = $this->dbOrNull();
        if (!$db) {
            return;
        }

        $stmt = $db->prepare('SELECT requests, window_start FROM security_rate_limits WHERE ip = ? LIMIT 1');
        $stmt->execute([$key]);
        $row = $stmt->fetch(\PDO::FETCH_ASSOC);
        $now = time();

        if (!$row) {
            $insert = $db->prepare('INSERT INTO security_rate_limits (ip, requests, window_start) VALUES (?, 1, ?)');
            $insert->execute([$key, $now]);
            return;
        }

        if (($now - (int)$row['window_start']) > self::WINDOW_SECONDS) {
            $update = $db->prepare('UPDATE security_rate_limits SET requests = 1, window_start = ? WHERE ip = ?');
            $update->execute([$now, $key]);
            return;
        }

        $update = $db->prepare('UPDATE security_rate_limits SET requests = requests + 1 WHERE ip = ?');
        $update->execute([$key]);
    }

    private function reset(string $key): void
    {
        $db = $this->dbOrNull();
        if (!$db) {
            return;
        }

        $db->prepare('DELETE FROM security_rate_limits WHERE ip = ?')->execute([$key]);
    }

    private function ipKey(string $ip): string
    {
        return 'login:ip:' . substr(hash('sha256', trim($ip)), 0, 32);
    }

    private function userKey(string $username): string
    {
        return 'login:user:' . substr(hash('sha256', mb_strtolower(trim($username), 'UTF-8')), 0, 32);
    }
}
