<?php
/**
 * سودانا — SecurityGate v1
 * Enforced security middleware for ALL endpoints
 *
 * Usage in any endpoint (replaces 5+ lines of boilerplate):
 *   require_once __DIR__.'/../../engine/SecurityGate.php';
 *   $user = SecurityGate::enforce(['auth' => true, 'csrf' => true, 'rate' => 'api']);
 */
require_once __DIR__ . '/Logger.php';

final class SecurityGate {

    /**
     * Main enforcement entry point.
     *
     * @param array $opts {
     *   auth: bool|'admin'|'optional' — require login (default: true)
     *   csrf: bool — require CSRF token on POST (default: false for JSON APIs)
     *   rate: string|false — rate limit bucket name ('api', 'auth', 'pay'...) (default: 'api')
     *   public: bool — explicitly mark endpoint as public, skips auth (default: false)
     * }
     * @return array|null user record (or null if public)
     */
    public static function enforce(array $opts = []): ?array {
        $opts = array_merge([
            'auth'   => true,
            'csrf'   => false,
            'rate'   => 'api',
            'public' => false,
        ], $opts);

        // 1. IP block check
        self::checkIpBlock();

        // 2. Rate limit
        if ($opts['rate']) {
            self::checkRateLimit($opts['rate']);
        }

        // 3. Public endpoint — return null user
        if ($opts['public']) {
            return null;
        }

        // 4. Auth check
        $user = null;
        if ($opts['auth'] === 'admin') {
            $user = self::requireAdmin();
        } elseif ($opts['auth'] === 'optional') {
            $user = function_exists('current_user') ? current_user() : null;
        } elseif ($opts['auth']) {
            $user = self::requireUser();
        }

        // 5. CSRF check (only if requested AND POST)
        if ($opts['csrf'] && $_SERVER['REQUEST_METHOD'] === 'POST') {
            self::checkCsrf();
        }

        // 6. Setup logger context
        if ($user) {
            Logger::setContext('user_id', $user['id'] ?? 0);
        }

        return $user;
    }

    /** Returns user array or dies with 401 */
    public static function requireUser(): array {
        if (!function_exists('current_user')) {
            self::deny(500, 'config_not_loaded', 'يجب تحميل config.php أولاً');
        }
        $u = current_user();
        if (!$u) {
            Logger::warn('auth_failed', ['ip' => $_SERVER['REMOTE_ADDR'] ?? '?']);
            self::deny(401, 'unauthorized', 'يجب تسجيل الدخول');
        }
        return $u;
    }

    /** Returns admin user or dies with 403 */
    public static function requireAdmin(): array {
        if (session_status() === PHP_SESSION_NONE) {
            if (function_exists('start_secure_session')) start_secure_session();
            else session_start();
        }
        $admin = $_SESSION['admin'] ?? null;
        if (!is_array($admin) || empty($admin['id'])) {
            Logger::warn('admin_auth_failed', ['ip' => $_SERVER['REMOTE_ADDR'] ?? '?']);
            self::deny(403, 'forbidden', 'صلاحية الإدارة مطلوبة');
        }
        return $admin;
    }

    /** Verify CSRF token from POST or header */
    public static function checkCsrf(): void {
        if (session_status() === PHP_SESSION_NONE) {
            if (function_exists('start_secure_session')) start_secure_session();
            else session_start();
        }
        $expected = $_SESSION['csrf_token'] ?? null;
        $received = $_POST['csrf_token']
                 ?? ($_SERVER['HTTP_X_CSRF_TOKEN'] ?? null);

        // For JSON API requests, also check JSON body
        if (!$received && stripos($_SERVER['CONTENT_TYPE'] ?? '', 'application/json') !== false) {
            $body = json_decode(file_get_contents('php://input') ?: '{}', true);
            $received = $body['csrf_token'] ?? null;
        }

        if (!$expected || !$received || !hash_equals($expected, $received)) {
            Logger::warn('csrf_invalid', ['ip' => $_SERVER['REMOTE_ADDR'] ?? '?']);
            self::deny(403, 'csrf_invalid', 'رمز الحماية غير صالح. أعد تحميل الصفحة.');
        }
    }

    /** Generate or fetch CSRF token for current session */
    public static function csrfToken(): string {
        if (session_status() === PHP_SESSION_NONE) {
            if (function_exists('start_secure_session')) start_secure_session();
            else session_start();
        }
        if (empty($_SESSION['csrf_token'])) {
            $_SESSION['csrf_token'] = bin2hex(random_bytes(16));
        }
        return $_SESSION['csrf_token'];
    }

    /** Rate limit by IP + bucket */
    public static function checkRateLimit(string $bucket): void {
        $limits = [
            'api'    => [60, 60],   // 60 requests / 60 seconds
            'auth'   => [10, 300],  // 10 / 5 min (login attempts)
            'pay'    => [20, 60],   // 20 / minute (payments)
            'admin'  => [120, 60],  // higher for admin
            'ai'     => [30, 60],   // 30 AI calls / minute
        ];
        [$maxReq, $window] = $limits[$bucket] ?? [60, 60];

        $ip = $_SERVER['REMOTE_ADDR'] ?? '0.0.0.0';
        $key = "rl:$bucket:" . md5($ip);

        if (!function_exists('check_rate_limit')) return;

        if (!check_rate_limit($key, $maxReq, $window)) {
            Logger::warn('rate_limit_exceeded', ['bucket' => $bucket, 'ip' => $ip]);
            self::deny(429, 'rate_limit',
                "تجاوزت الحد. جرّب بعد دقيقة.",
                ['Retry-After: ' . $window]);
        }
    }

    /** Block known-bad IPs (basic) */
    public static function checkIpBlock(): void {
        $ip = $_SERVER['REMOTE_ADDR'] ?? '';
        if (!$ip) return;

        try {
            $st = db()->prepare("SELECT 1 FROM ip_blocks WHERE ip=? AND expires_at>NOW() LIMIT 1");
            $st->execute([$ip]);
            if ($st->fetch()) {
                Logger::warn('ip_blocked', ['ip' => $ip]);
                self::deny(403, 'ip_blocked', 'تم حظر عنوانك مؤقتاً');
            }
        } catch (Throwable $ignored) {
            // Table may not exist yet — graceful skip
        }
    }

    /** Helper: send error response and exit */
    private static function deny(int $code, string $error, string $message, array $headers = []): void {
        http_response_code($code);
        foreach ($headers as $h) header($h);
        if (!headers_sent()) {
            header('Content-Type: application/json; charset=utf-8');
        }
        die(json_encode([
            'error'    => $error,
            'message'  => $message,
            'trace_id' => Logger::traceId(),
        ], JSON_UNESCAPED_UNICODE));
    }
}
