<?php

namespace App\Core;

use PDO;

/**
 * Rate Limiter - تحديد معدل الطلبات
 */
class RateLimiter
{
    private PDO $db;
    private string $tableName = 'rate_limit_logs';

    public function __construct(PDO $db)
    {
        $this->db = $db;
        $this->ensureTableExists();
    }

    /**
     * التأكد من وجود جدول rate limiting
     */
    private function ensureTableExists(): void
    {
        try {
            $this->db->exec("
                CREATE TABLE IF NOT EXISTS {$this->tableName} (
                    id INT AUTO_INCREMENT PRIMARY KEY,
                    identifier VARCHAR(255) NOT NULL,
                    action_type VARCHAR(100) NOT NULL,
                    attempts INT NOT NULL DEFAULT 1,
                    window_start DATETIME NOT NULL,
                    blocked_until DATETIME NULL,
                    created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
                    INDEX idx_identifier_action (identifier, action_type),
                    INDEX idx_window_start (window_start),
                    INDEX idx_blocked_until (blocked_until)
                ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4
            ");
        } catch (\Throwable $e) {
            // الجدول موجود بالفعل أو خطأ في الإنشاء
        }
    }

    /**
     * التحقق من السماح بالطلب
     * 
     * @param string $identifier المعرف (IP, user_id, etc.)
     * @param string $actionType نوع الإجراء (login, api_call, search, etc.)
     * @param int $maxAttempts الحد الأقصى للمحاولات
     * @param int $windowSeconds نافذة الوقت بالثواني
     * @return array ['allowed' => bool, 'remaining' => int, 'reset_at' => string|null]
     */
    public function check(string $identifier, string $actionType, int $maxAttempts, int $windowSeconds): array
    {
        $now = date('Y-m-d H:i:s');
        $windowStart = date('Y-m-d H:i:s', time() - $windowSeconds);

        // التحقق من الحظر
        $stmt = $this->db->prepare("
            SELECT blocked_until 
            FROM {$this->tableName} 
            WHERE identifier = ? 
            AND action_type = ? 
            AND blocked_until IS NOT NULL 
            AND blocked_until > NOW()
            ORDER BY blocked_until DESC 
            LIMIT 1
        ");
        $stmt->execute([$identifier, $actionType]);
        $blocked = $stmt->fetch();

        if ($blocked && $blocked['blocked_until'] > $now) {
            return [
                'allowed' => false,
                'remaining' => 0,
                'reset_at' => $blocked['blocked_until'],
                'blocked' => true
            ];
        }

        // حساب المحاولات في النافذة الحالية
        $stmt = $this->db->prepare("
            SELECT SUM(attempts) AS total_attempts, MAX(window_start) AS last_window
            FROM {$this->tableName} 
            WHERE identifier = ? 
            AND action_type = ? 
            AND window_start >= ?
        ");
        $stmt->execute([$identifier, $actionType, $windowStart]);
        $result = $stmt->fetch();

        $totalAttempts = (int)($result['total_attempts'] ?? 0);
        $remaining = max(0, $maxAttempts - $totalAttempts);
        $allowed = $remaining > 0;

        // حساب وقت إعادة التعيين
        $resetAt = null;
        if ($result['last_window']) {
            $resetAt = date('Y-m-d H:i:s', strtotime($result['last_window']) + $windowSeconds);
        }

        return [
            'allowed' => $allowed,
            'remaining' => $remaining,
            'reset_at' => $resetAt,
            'blocked' => false
        ];
    }

    /**
     * تسجيل محاولة
     */
    public function record(string $identifier, string $actionType, int $windowSeconds, ?int $blockDurationSeconds = null): void
    {
        $now = date('Y-m-d H:i:s');
        $windowStart = date('Y-m-d H:i:s', time() - ($windowSeconds % 3600)); // بداية الساعة الحالية

        // البحث عن سجل في نفس النافذة
        $stmt = $this->db->prepare("
            SELECT id, attempts 
            FROM {$this->tableName} 
            WHERE identifier = ? 
            AND action_type = ? 
            AND window_start = ?
            LIMIT 1
        ");
        $stmt->execute([$identifier, $actionType, $windowStart]);
        $existing = $stmt->fetch();

        if ($existing) {
            // تحديث المحاولات
            $newAttempts = (int)$existing['attempts'] + 1;
            $blockedUntil = null;
            
            if ($blockDurationSeconds !== null) {
                $blockedUntil = date('Y-m-d H:i:s', time() + $blockDurationSeconds);
            }

            $stmt = $this->db->prepare("
                UPDATE {$this->tableName} 
                SET attempts = ?, blocked_until = ?
                WHERE id = ?
            ");
            $stmt->execute([$newAttempts, $blockedUntil, $existing['id']]);
        } else {
            // إنشاء سجل جديد
            $blockedUntil = null;
            if ($blockDurationSeconds !== null) {
                $blockedUntil = date('Y-m-d H:i:s', time() + $blockDurationSeconds);
            }

            $stmt = $this->db->prepare("
                INSERT INTO {$this->tableName} 
                (identifier, action_type, attempts, window_start, blocked_until) 
                VALUES (?, ?, 1, ?, ?)
            ");
            $stmt->execute([$identifier, $actionType, $windowStart, $blockedUntil]);
        }

        // تنظيف السجلات القديمة (أقدم من 24 ساعة)
        $this->cleanup();
    }

    /**
     * تنظيف السجلات القديمة
     */
    private function cleanup(): void
    {
        try {
            $this->db->exec("
                DELETE FROM {$this->tableName} 
                WHERE window_start < DATE_SUB(NOW(), INTERVAL 24 HOUR)
                AND (blocked_until IS NULL OR blocked_until < NOW())
            ");
        } catch (\Throwable $e) {
            // تجاهل الأخطاء في التنظيف
        }
    }

    /**
     * إزالة الحظر يدوياً
     */
    public function unblock(string $identifier, string $actionType): bool
    {
        try {
            $stmt = $this->db->prepare("
                UPDATE {$this->tableName} 
                SET blocked_until = NULL 
                WHERE identifier = ? 
                AND action_type = ?
            ");
            return $stmt->execute([$identifier, $actionType]);
        } catch (\Throwable $e) {
            return false;
        }
    }

    /**
     * جلب IP address للعميل
     */
    public static function getClientIp(): string
    {
        $ipKeys = ['HTTP_CLIENT_IP', 'HTTP_X_FORWARDED_FOR', 'HTTP_X_FORWARDED', 'HTTP_X_CLUSTER_CLIENT_IP', 'HTTP_FORWARDED_FOR', 'HTTP_FORWARDED', 'REMOTE_ADDR'];
        
        foreach ($ipKeys as $key) {
            if (array_key_exists($key, $_SERVER) === true) {
                foreach (explode(',', $_SERVER[$key]) as $ip) {
                    $ip = trim($ip);
                    if (filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE) !== false) {
                        return $ip;
                    }
                }
            }
        }
        
        return $_SERVER['REMOTE_ADDR'] ?? '0.0.0.0';
    }
}
