<?php
/**
 * CSRF Protection Class
 * 
 * Provides CSRF token generation and validation
 * All AJAX requests MUST include a valid CSRF token
 * 
 * @author ESDN Security Team
 * @version 1.0.0
 */

class Csrf
{

    private const TOKEN_NAME = 'csrf_token';
    private const TOKEN_TIME_NAME = 'csrf_token_time';
    private const TOKEN_LIFETIME = 3600; // 1 hour

    /**
     * Generate a new CSRF token
     * 
     * @return string The generated token
     */
    public static function generateToken(): string
    {
        if (session_status() === PHP_SESSION_NONE) {
            session_start();
        }

        $token = bin2hex(random_bytes(32));
        $_SESSION[self::TOKEN_NAME] = $token;
        $_SESSION[self::TOKEN_TIME_NAME] = time();

        return $token;
    }

    /**
     * Get the current CSRF token
     * Generates a new one if it doesn't exist or has expired
     * 
     * @return string The current token
     */
    public static function getToken(): string
    {
        if (session_status() === PHP_SESSION_NONE) {
            session_start();
        }

        // Check if token exists and is still valid
        if (
            !isset($_SESSION[self::TOKEN_NAME]) ||
            !isset($_SESSION[self::TOKEN_TIME_NAME]) ||
            (time() - $_SESSION[self::TOKEN_TIME_NAME]) > self::TOKEN_LIFETIME
        ) {
            return self::generateToken();
        }

        return $_SESSION[self::TOKEN_NAME];
    }

    /**
     * Validate a CSRF token
     * 
     * @param string $token The token to validate
     * @return bool True if valid, false otherwise
     */
    public static function validateToken(string $token): bool
    {
        if (session_status() === PHP_SESSION_NONE) {
            session_start();
        }

        // Check if token exists in session
        if (!isset($_SESSION[self::TOKEN_NAME]) || !isset($_SESSION[self::TOKEN_TIME_NAME])) {
            return false;
        }

        // Check if token has expired
        if ((time() - $_SESSION[self::TOKEN_TIME_NAME]) > self::TOKEN_LIFETIME) {
            return false;
        }

        // Use hash_equals to prevent timing attacks
        return hash_equals($_SESSION[self::TOKEN_NAME], $token);
    }

    /**
     * Validate token from request
     * Checks both POST and headers for token
     * 
     * @return bool True if valid, false otherwise
     */
    public static function validateRequest(): bool
    {
        // 1. Check Custom Header first (modern AJAX)
        if (isset($_SERVER['HTTP_X_CSRF_TOKEN'])) {
            return self::validateToken($_SERVER['HTTP_X_CSRF_TOKEN']);
        }

        // 2. Check POST data (standard form submission)
        if (isset($_POST[self::TOKEN_NAME])) {
            return self::validateToken($_POST[self::TOKEN_NAME]);
        }

        // 3. Check JSON payload (fallback for older AJAX/manual fetch)
        if (
            isset($_SERVER['CONTENT_TYPE']) &&
            strpos($_SERVER['CONTENT_TYPE'], 'application/json') !== false
        ) {

            $json = file_get_contents('php://input');
            $data = json_decode($json, true);
            if (isset($data[self::TOKEN_NAME])) {
                return self::validateToken($data[self::TOKEN_NAME]);
            }
        }

        return false;
    }

    /**
     * Generate HTML hidden input field with CSRF token
     * 
     * @return string HTML input field
     */
    public static function getTokenField(): string
    {
        $token = self::getToken();
        return '<input type="hidden" name="' . self::TOKEN_NAME . '" value="' . htmlspecialchars($token, ENT_QUOTES, 'UTF-8') . '">';
    }

    /**
     * Get token for JavaScript/AJAX usage
     * 
     * @return array Array with token name and value
     */
    public static function getTokenForAjax(): array
    {
        return [
            'name' => self::TOKEN_NAME,
            'value' => self::getToken()
        ];
    }

    /**
     * Regenerate token (call after successful form submission)
     * 
     * @return string New token
     */
    public static function regenerateToken(): string
    {
        return self::generateToken();
    }

    /**
     * Clear CSRF token from session
     */
    public static function clearToken(): void
    {
        if (session_status() === PHP_SESSION_NONE) {
            session_start();
        }

        unset($_SESSION[self::TOKEN_NAME]);
        unset($_SESSION[self::TOKEN_TIME_NAME]);
    }
}
