<?php

declare(strict_types=1);

namespace Core;

/**
 * Request — typed input access with CSRF protection and XSS sanitisation.
 */
final class Request
{
    // ── Input ─────────────────────────────────────────────────────────────────

    public static function get(string $key, mixed $default = null): mixed
    {
        return $_GET[$key] ?? $default;
    }

    public static function post(string $key, mixed $default = null): mixed
    {
        return $_POST[$key] ?? $default;
    }

    public static function input(string $key, mixed $default = null): mixed
    {
        return $_POST[$key] ?? $_GET[$key] ?? $default;
    }

    public static function all(): array
    {
        return array_merge($_GET, $_POST);
    }

    /** Return sanitised string — strips tags and encodes HTML entities. */
    public static function str(string $key, string $default = ''): string
    {
        $value = self::input($key, $default);
        return htmlspecialchars(strip_tags((string) $value), ENT_QUOTES, 'UTF-8');
    }

    public static function int(string $key, int $default = 0): int
    {
        return (int) self::input($key, $default);
    }

    public static function float(string $key, float $default = 0.0): float
    {
        return (float) self::input($key, $default);
    }

    public static function bool(string $key, bool $default = false): bool
    {
        $val = self::input($key);
        if ($val === null) return $default;
        return filter_var($val, FILTER_VALIDATE_BOOLEAN);
    }

    public static function has(string $key): bool
    {
        return isset($_POST[$key]) || isset($_GET[$key]);
    }

    public static function isPost(): bool
    {
        return strtoupper($_SERVER['REQUEST_METHOD'] ?? '') === 'POST';
    }

    public static function isGet(): bool
    {
        return strtoupper($_SERVER['REQUEST_METHOD'] ?? '') === 'GET';
    }

    public static function isAjax(): bool
    {
        return strtolower($_SERVER['HTTP_X_REQUESTED_WITH'] ?? '') === 'xmlhttprequest';
    }

    // ── Files ─────────────────────────────────────────────────────────────────

    /** @return array<string, mixed>|null */
    public static function file(string $key): ?array
    {
        if (!isset($_FILES[$key]) || $_FILES[$key]['error'] === UPLOAD_ERR_NO_FILE) {
            return null;
        }
        return $_FILES[$key];
    }

    // ── CSRF ──────────────────────────────────────────────────────────────────

    public static function generateCsrfToken(): string
    {
        if (!Session::has(CSRF_TOKEN_NAME) || self::csrfExpired()) {
            $token = bin2hex(random_bytes(32));
            Session::set(CSRF_TOKEN_NAME, $token);
            Session::set(CSRF_TOKEN_NAME . '_time', time());
        }
        return Session::get(CSRF_TOKEN_NAME);
    }

    public static function validateCsrf(): bool
    {
        // Accept _csrf_token (forms) or _csrf (AJAX shorthand) or X-CSRF-TOKEN header
        $token  = $_POST[CSRF_TOKEN_NAME] ?? $_POST['_csrf'] ?? self::bearerToken();
        $stored = Session::get(CSRF_TOKEN_NAME);

        if (!$token || !$stored) {
            return false;
        }

        if (self::csrfExpired()) {
            Session::remove(CSRF_TOKEN_NAME);
            return false;
        }

        return hash_equals($stored, $token);
    }

    public static function verifyCsrf(): void
    {
        if (!self::validateCsrf()) {
            http_response_code(419);
            $isAjax = self::isAjax()
                || str_contains($_SERVER['HTTP_ACCEPT'] ?? '', 'application/json')
                || str_contains($_SERVER['HTTP_CONTENT_TYPE'] ?? $_SERVER['CONTENT_TYPE'] ?? '', 'application/json');

            if ($isAjax) {
                header('Content-Type: application/json');
                die(json_encode(['success' => false, 'message' => 'Session expired. Please refresh the page.']));
            }

            // For regular form posts: redirect back with error flash
            Session::flash('error', 'Your session expired. Please try again.');
            $referer = $_SERVER['HTTP_REFERER'] ?? null;
            if ($referer) {
                header('Location: ' . $referer);
            } else {
                header('Location: ' . base_url() . '/dashboard');
            }
            exit;
        }
    }

    private static function csrfExpired(): bool
    {
        $time = Session::get(CSRF_TOKEN_NAME . '_time', 0);
        return (time() - (int) $time) > CSRF_TOKEN_EXP;
    }

    private static function bearerToken(): ?string
    {
        $header = $_SERVER['HTTP_X_CSRF_TOKEN'] ?? null;
        return $header;
    }

    // ── Helpers ───────────────────────────────────────────────────────────────

    public static function ip(): string
    {
        foreach (['HTTP_CLIENT_IP', 'HTTP_X_FORWARDED_FOR', 'REMOTE_ADDR'] as $key) {
            if (!empty($_SERVER[$key])) {
                return (string) filter_var(
                    explode(',', $_SERVER[$key])[0],
                    FILTER_VALIDATE_IP
                );
            }
        }
        return '0.0.0.0';
    }
}
