<?php

/**
 * Security Helpers — OTIS Life System
 * CSRF, XSS, Rate Limiting, Input Sanitization
 */

// ─── 1. CSRF Protection ────────────────────────────────────────
function generate_csrf_token(): string {
    if (empty($_SESSION['csrf_token'])) {
        $_SESSION['csrf_token'] = bin2hex(random_bytes(32));
    }
    return $_SESSION['csrf_token'];
}

function verify_csrf_token(string $token): void {
    if (empty($_SESSION['csrf_token']) || !hash_equals($_SESSION['csrf_token'], $token)) {
        http_response_code(403);
        header('Content-Type: text/html; charset=utf-8');
        die('<!DOCTYPE html><html dir="rtl"><head><meta charset="UTF-8"><title>خطأ أمني</title></head>
             <body style="font-family:sans-serif;direction:rtl;padding:40px;text-align:center;">
             <h2 style="color:#c0392b;">⛔ خطأ أمني</h2>
             <p>انتهت صلاحية الجلسة أو رمز الحماية غير صالح. <a href="javascript:history.back()">← رجوع</a></p>
             </body></html>');
    }
}

function csrf_field(): string {
    $token = generate_csrf_token();
    return '<input type="hidden" name="csrf_token" value="' . htmlspecialchars($token, ENT_QUOTES, 'UTF-8') . '">';
}

// ─── 2. Input Sanitization (XSS Prevention) ──────────────────
function sanitize_input($data) {
    if (is_array($data)) {
        return array_map('sanitize_input', $data);
    }
    return htmlspecialchars(stripslashes(trim((string)$data)), ENT_QUOTES, 'UTF-8');
}

function get_secure_post(?string $key = null) {
    if ($key === null) return sanitize_input($_POST);
    return isset($_POST[$key]) ? sanitize_input($_POST[$key]) : null;
}

// ─── 3. Password Hashing ──────────────────────────────────────
function hash_secure_password(string $password): string {
    return password_hash($password, PASSWORD_BCRYPT, ['cost' => 12]);
}

function verify_secure_password(string $password, string $hash): bool {
    return password_verify($password, $hash);
}

// ─── 4. Rate Limiting (DB-backed, Namecheap-compatible) ──────
/**
 * Check if an IP has exceeded login attempt limit.
 * @param string $ip     IP address
 * @param int    $max    Max attempts (default 5)
 * @param int    $window Window in seconds (default 900 = 15 min)
 * @return bool  true = blocked, false = allowed
 */
function is_rate_limited(string $ip, int $max = 5, int $window = 900): bool {
    try {
        $db = \Core\Database::getInstance();
        // Clean old attempts first
        $db->query(
            "DELETE FROM login_attempts WHERE attempted_at < DATE_SUB(NOW(), INTERVAL ? SECOND)",
            [$window]
        );
        $count = $db->query(
            "SELECT COUNT(*) FROM login_attempts WHERE ip_address = ? AND attempted_at >= DATE_SUB(NOW(), INTERVAL ? SECOND)",
            [$ip, $window]
        )->fetchColumn();
        return (int)$count >= $max;
    } catch (\Throwable $e) {
        error_log('[OTIS RateLimit] ' . $e->getMessage());
        return false; // Fail open — don't block if table missing
    }
}

/**
 * Record a failed login attempt.
 */
function record_login_attempt(string $ip, string $username = ''): void {
    try {
        $db = \Core\Database::getInstance();
        $db->query(
            "INSERT INTO login_attempts (ip_address, username) VALUES (?, ?)",
            [$ip, $username]
        );
    } catch (\Throwable $e) {
        error_log('[OTIS RateLimit Record] ' . $e->getMessage());
    }
}

/**
 * Clear login attempts on successful login.
 */
function clear_login_attempts(string $ip): void {
    try {
        $db = \Core\Database::getInstance();
        $db->query("DELETE FROM login_attempts WHERE ip_address = ?", [$ip]);
    } catch (\Throwable $e) {}
}

/**
 * Get remaining attempts before lockout.
 */
function remaining_attempts(string $ip, int $max = 5, int $window = 900): int {
    try {
        $db = \Core\Database::getInstance();
        $count = $db->query(
            "SELECT COUNT(*) FROM login_attempts WHERE ip_address = ? AND attempted_at >= DATE_SUB(NOW(), INTERVAL ? SECOND)",
            [$ip, $window]
        )->fetchColumn();
        return max(0, $max - (int)$count);
    } catch (\Throwable $e) {
        return $max;
    }
}
