<?php

/**
 * 🔹 دالة client_ip
 * الغرض: الحصول على عنوان الـ IP الحقيقي للعميل حتى لو كان خلف Proxy.
 * 
 * المنطق:
 *  - إذا كان الهيدر HTTP_X_FORWARDED_FOR موجودًا، يتم أخد أول IP من القائمة (الأصلي).
 *  - إن لم يكن، يتم استخدام REMOTE_ADDR.
 *  - إذا لم يوجد أي منهما، يتم إرجاع 'unknown'.
 */
function client_ip() {
    if (!empty($_SERVER['HTTP_X_FORWARDED_FOR'])) {
        // X-Forwarded-For قد يحتوي على أكثر من IP مفصولة بفاصلة، نأخذ الأول فقط (الأصلي)
        $ips = explode(',', $_SERVER['HTTP_X_FORWARDED_FOR']);
        return trim($ips[0]);
    }
    if (!empty($_SERVER['REMOTE_ADDR'])) {
        return $_SERVER['REMOTE_ADDR'];
    }
    return 'unknown';
}

/**
 * 🔹 دالة normalize_request_body
 * الغرض: تحويل محتوى الطلب (raw_data) إلى صيغة JSON ثابتة لاستخدامها في حساب البصمة (hash)
 * 
 * تفاصيل:
 *  - إذا كانت البيانات نصية (string)، يتم محاولة تحويلها إلى مصفوفة ثم إعادة ترميزها بصيغة JSON منظمة.
 *  - إذا كانت مصفوفة أو كائن (array / object)، يتم تحويلها إلى JSON مباشرة.
 *  - إذا كانت قيمة بسيطة (عدد أو نص قصير)، يتم تحويلها إلى نص.
 * 
 * الهدف: توحيد شكل البيانات لتجنب اختلاف ترتيب المفاتيح عند حساب التجزئة.
 */
function normalize_request_body($raw_data) {
    if (is_string($raw_data)) {
        $decoded = json_decode($raw_data, true);
        if (json_last_error() === JSON_ERROR_NONE && is_array($decoded)) {
            // إعادة الترميز بشكل منظم (بحسب ترتيب PHP الافتراضي)
            return json_encode($decoded, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
        }
        // إذا لم يكن JSON صالحًا، نعيد النص كما هو
        return $raw_data;
    } elseif (is_array($raw_data) || is_object($raw_data)) {
        return json_encode($raw_data, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
    } else {
        // إذا كانت قيمة بسيطة
        return strval($raw_data);
    }
}

/**
 * 🔹 دالة check_spam
 * الغرض: التحقق مما إذا كان المستخدم يرسل طلبات كثيرة جدًا أو مكررة خلال فترة قصيرة.
 * 
 * القواعد المطبقة:
 *  1️⃣ تحديد عدد أقصى من الطلبات خلال فترة زمنية محددة (limit_requests).
 *  2️⃣ منع تكرار نفس الطلب (نفس التجزئة) خلال فترة زمنية قصيرة (duplicate_window).
 * 
 * المعاملات:
 *  - $conn: اتصال قاعدة البيانات.
 *  - $user_id: رقم المستخدم (اختياري).
 *  - $endpoint: اسم العملية أو الصفحة التي يتم تنفيذها.
 *  - $raw_data: بيانات الطلب الأصلية (لإنشاء البصمة).
 * 
 * الإرجاع:
 *  مصفوفة تحتوي على:
 *   ['block' => bool, 'reason' => string]
 * 
 * الملاحظات:
 *  - يستخدم استعلامات محضرة (Prepared Statements).
 *  - يعتمد على الوقت الحالي من الخادم (NOW()).
 */
function check_spam($conn, $user_id = null, $endpoint = '', $raw_data = '') {
    $ip = client_ip();
    $ua = $_SERVER['HTTP_USER_AGENT'] ?? 'unknown';
    $request_body = normalize_request_body($raw_data);
    $request_hash = hash('sha256', $request_body);

    // إعداد القيم المحددة للحدود الزمنية والعددية
    $limit_requests = 10;               // الحد الأقصى للطلبات
    $limit_window_minutes = 1;          // النافذة الزمنية بالدقائق
    $duplicate_window_minutes = 3;      // فترة فحص التكرار بالدقائق

    // 🔸 الخطوة الأولى: حساب عدد الطلبات من نفس الـ IP خلال الدقيقة الأخيرة
    $sql = "SELECT COUNT(*) AS cnt FROM requests_log WHERE ip_address = ? AND created_at > (NOW() - INTERVAL ? MINUTE)";
    $stmt = mysqli_prepare($conn, $sql);
    if (!$stmt) {
        error_log("check_spam prepare failed: " . mysqli_error($conn));
        // في حال فشل تنفيذ الاستعلام، لا نمنع المستخدم (fail open)
        return ['block' => false];
    }
    mysqli_stmt_bind_param($stmt, "si", $ip, $limit_window_minutes);
    mysqli_stmt_execute($stmt);
    $res = mysqli_stmt_get_result($stmt);
    $row = mysqli_fetch_assoc($res);
	$row = clean_array($row);
    mysqli_stmt_close($stmt);

    $cnt1 = isset($row['cnt']) ? (int)$row['cnt'] : 0;
    if ($cnt1 >= $limit_requests) {
        return ['block' => true, 'reason' => "طلبات كثيرة جدًا خلال فترة قصيرة"];
    }

    // 🔸 الخطوة الثانية: فحص تكرار نفس الطلب (نفس التجزئة)
    $sql2 = "SELECT COUNT(*) AS cnt FROM requests_log WHERE request_hash = ? AND ip_address = ? AND created_at > (NOW() - INTERVAL ? MINUTE)";
    $stmt2 = mysqli_prepare($conn, $sql2);
    if (!$stmt2) {
        error_log("check_spam prepare2 failed: " . mysqli_error($conn));
        return ['block' => false];
    }
    mysqli_stmt_bind_param($stmt2, "ssi", $request_hash, $ip, $duplicate_window_minutes);
    mysqli_stmt_execute($stmt2);
    $res2 = mysqli_stmt_get_result($stmt2);
    $row2 = mysqli_fetch_assoc($res2);
	$row2 = clean_array($row2);
    mysqli_stmt_close($stmt2);

    $dup = isset($row2['cnt']) ? (int)$row2['cnt'] : 0;
    if ($dup > 0) {
        return ['block' => true, 'reason' => "تكرار نفس الطلب خلال فترة قصيرة"];
    }

    // إذا لم تنطبق أي من الشروط السابقة، نسمح بالمتابعة
    return ['block' => false];
}

/**
 * 🔹 دالة log_request
 * الغرض: تسجيل تفاصيل الطلب في جدول requests_log لأغراض المراقبة والحماية.
 * 
 * السلوك:
 *  - تحفظ عنوان IP، المستخدم، التجزئة، جسم الطلب، المتصفح، وحالة التنفيذ.
 *  - لا يتم تنفيذ COMMIT هنا، بل يترك التحكم في المعاملة للوظيفة المستدعية.
 * 
 * المعاملات:
 *  - $conn: اتصال قاعدة البيانات.
 *  - $user_id: رقم المستخدم (اختياري).
 *  - $endpoint: اسم العملية.
 *  - $raw_data: بيانات الطلب.
 *  - $status: حالة التنفيذ ("success" أو "fail").
 * 
 * المخرجات:
 *  - TRUE في حال النجاح.
 *  - FALSE في حال فشل الإدخال.
 */
function log_request($conn, $new_id , $user_id = null, $endpoint = '', $raw_data = '', $status = 'success') {
    $ip = client_ip();
    $ua = $_SERVER['HTTP_USER_AGENT'] ?? 'unknown';
    $request_body = normalize_request_body($raw_data);
    $request_hash = hash('sha256', $request_body);

    // تحديد الحد الأقصى لطول النص المخزن (لمنع التخزين الزائد)
    $max_body_length = 1000000; // 1 ميغابايت
    if (strlen($request_body) > $max_body_length) {
        // يتم قطع النص مع الاحتفاظ بالبداية فقط
        $request_body = mb_substr($request_body, 0, $max_body_length, 'UTF-8');
    }
	
	
    $sql = "INSERT INTO requests_log (user_id, new_id, ip_address, endpoint, request_hash, request_body, user_agent, status, created_at)
            VALUES (?,?, ?, ?, ?, ?, ?, ?, NOW())";

    $stmt = mysqli_prepare($conn, $sql);
    if (!$stmt) {
        error_log("log_request prepare failed: " . mysqli_error($conn));
        return false;
    }

    // تحويل user_id إلى عدد صحيح أو null
    $uid = is_null($user_id) ? null : intval($user_id);

    // ربط المعاملات بالقيم بشكل آمن
    mysqli_stmt_bind_param($stmt, "iissssss",
        $uid,
		$new_id,
        $ip,
        $endpoint,
        $request_hash,
        $request_body,
        $ua,
        $status
    );

    $ok = mysqli_stmt_execute($stmt);
    if (!$ok) {
        error_log("log_request execute failed: " . mysqli_stmt_error($stmt));
    }

    mysqli_stmt_close($stmt);
    return (bool)$ok;
}

// 🔹 تنفيذ التحقق من السبام قبل الاستمرار في المعالجة
$check_spam = check_spam($conn, $user_id, $operation, $raw_data);


$check = check_spam($conn, $user_id, 'add_question', $raw_data);
?>
