<?php

namespace KeyLair;

class Csrf
{
    private const SESSION_KEY = 'csrf_token';

    public static function token(): string
    {
        if (empty($_SESSION[self::SESSION_KEY])) {
            $_SESSION[self::SESSION_KEY] = bin2hex(random_bytes(32));
        }

        return $_SESSION[self::SESSION_KEY];
    }

    public static function field(): string
    {
        return '<input type="hidden" name="csrf_token" value="' . Helpers::e(self::token()) . '">';
    }

    public static function validate(?string $submitted): bool
    {
        if (!$submitted || empty($_SESSION[self::SESSION_KEY])) {
            return false;
        }

        return hash_equals($_SESSION[self::SESSION_KEY], $submitted);
    }

    /**
     * When a POST body exceeds PHP's post_max_size, PHP silently discards the
     * entire request — $_POST and $_FILES both come back empty, including the
     * CSRF token, even though the browser did send a Content-Length. Detecting
     * that case lets us show a real explanation instead of a bogus CSRF error.
     */
    public static function postSizeExceeded(): bool
    {
        return ($_SERVER['REQUEST_METHOD'] ?? '') === 'POST'
            && empty($_POST)
            && empty($_FILES)
            && (int) ($_SERVER['CONTENT_LENGTH'] ?? 0) > 0;
    }

    /** Call at the top of every POST handler. Ends the request with 403 on failure. */
    public static function requireValid(): void
    {
        if (self::postSizeExceeded()) {
            self::failBackToForm('The upload was too large for the server to accept in one request. Please upload fewer files, or smaller ones, at a time.');
        }

        $submitted = $_POST['csrf_token'] ?? null;

        if (!self::validate($submitted)) {
            self::failBackToForm('Your form submission expired or could not be verified. Please try again.');
        }
    }

    /** Flash an inline error and redirect back to the same page (GET), instead of a dead-end error page. */
    private static function failBackToForm(string $message): void
    {
        $_SESSION['flash_error'] = $message;
        $target = $_SERVER['REQUEST_URI'] ?? '/';
        header('Location: ' . $target);
        exit;
    }
}
