<?php
// ==========================================================
// CORE CONFIG
// ==========================================================
require_once __DIR__ . '/config.php';

// Secrets are loaded from .env via config.php
defined('ENCRYPTION_KEY') or define('ENCRYPTION_KEY', 'default_insecure_key_do_not_use');
defined('ADMIN_USERNAME') or define('ADMIN_USERNAME', 'admin@example.com');
defined('ADMIN_PASSWORD') or define('ADMIN_PASSWORD', 'admin');

// ==========================================================
// ENCRYPTION HELPERS
// ==========================================================
function encryptData($plainText) {
    if (empty($plainText)) return '';
    $iv = openssl_random_pseudo_bytes(16);
    $encrypted = openssl_encrypt($plainText, 'AES-256-CBC', ENCRYPTION_KEY, 0, $iv);
    return base64_encode($iv . $encrypted);
}

function decryptData($encryptedText) {
    if (empty($encryptedText)) return '';
    $data = base64_decode($encryptedText);
    $iv = substr($data, 0, 16);
    $encrypted = substr($data, 16);
    return openssl_decrypt($encrypted, 'AES-256-CBC', ENCRYPTION_KEY, 0, $iv);
}

// ==========================================================
// USER HELPERS
// ==========================================================

function migrateUserSchema($user) {
    if ($user === null) return null;
    if (is_string($user['api_keys'])) {
        $user['api_keys'] = json_decode($user['api_keys'], true) ?: [];
    }
    if (!isset($user['api_keys']) || !is_array($user['api_keys'])) {
        $user['api_keys'] = [];
        if (!empty($user['api_key'])) {
            $user['api_keys'][] = [
                'key' => $user['api_key'],
                'label' => 'Default',
                'created_at' => $user['created_at'] ?? nowIST()->format('Y-m-d H:i:s')
            ];
        }
    }
    if (!isset($user['reset_chances'])) $user['reset_chances'] = 100;
    if (!isset($user['avatar'])) $user['avatar'] = '';
    if (!isset($user['webhook_url'])) $user['webhook_url'] = '';
    if (!isset($user['support_link'])) $user['support_link'] = '';
    return $user;
}

function getUserByEmail($email) {
    $stmt = getDB()->prepare("SELECT * FROM users WHERE email = ?");
    $stmt->execute([$email]);
    $user = $stmt->fetch();
    return $user ? migrateUserSchema($user) : null;
}

function getUserById($id) {
    $stmt = getDB()->prepare("SELECT * FROM users WHERE id = ?");
    $stmt->execute([$id]);
    $user = $stmt->fetch();
    return $user ? migrateUserSchema($user) : null;
}

function saveUser($userData) {
    // Auto-add columns if missing
    static $colsChecked = false;
    if (!$colsChecked) {
        $colsChecked = true;
        try { getDB()->exec("ALTER TABLE users ADD COLUMN webhook_url TEXT DEFAULT ''"); } catch (\Exception $e) {}
        try { getDB()->exec("ALTER TABLE users ADD COLUMN reset_token VARCHAR(255) DEFAULT NULL"); } catch (\Exception $e) {}
        try { getDB()->exec("ALTER TABLE users ADD COLUMN reset_expires INT DEFAULT NULL"); } catch (\Exception $e) {}
        try { getDB()->exec("ALTER TABLE users ADD COLUMN phone VARCHAR(20) DEFAULT ''"); } catch (\Exception $e) {}
        try { getDB()->exec("ALTER TABLE users ADD COLUMN google_linked TINYINT(1) DEFAULT 0"); } catch (\Exception $e) {}
        try { getDB()->exec("ALTER TABLE users ADD COLUMN support_link VARCHAR(255) DEFAULT ''"); } catch (\Exception $e) {}

    }

    $sql = "INSERT INTO users (
                id, name, email, phone, password_hash, created_at, fampay_gmail, 
                fampay_app_password_encrypted, fampay_upi_id, gmail_connected, google_linked, 
                api_keys, reset_chances, avatar, plan_id, plan_label, plan_expires_at, 
                total_requests, total_success, total_failed, total_amount_received, status, webhook_url, reset_token, reset_expires, support_link
            ) VALUES (
                :id, :name, :email, :phone, :password_hash, :created_at, :fampay_gmail, 
                :fampay_app_password_encrypted, :fampay_upi_id, :gmail_connected, :google_linked, 
                :api_keys, :reset_chances, :avatar, :plan_id, :plan_label, :plan_expires_at, 
                :total_requests, :total_success, :total_failed, :total_amount_received, :status, :webhook_url, :reset_token, :reset_expires, :support_link
            ) ON DUPLICATE KEY UPDATE 
                name=VALUES(name), email=VALUES(email), phone=VALUES(phone), password_hash=VALUES(password_hash),
                fampay_gmail=VALUES(fampay_gmail), fampay_app_password_encrypted=VALUES(fampay_app_password_encrypted),
                fampay_upi_id=VALUES(fampay_upi_id), gmail_connected=VALUES(gmail_connected), google_linked=VALUES(google_linked),
                api_keys=VALUES(api_keys), reset_chances=VALUES(reset_chances), avatar=VALUES(avatar),
                plan_id=VALUES(plan_id), plan_label=VALUES(plan_label), plan_expires_at=VALUES(plan_expires_at),
                total_requests=VALUES(total_requests), total_success=VALUES(total_success), 
                total_failed=VALUES(total_failed), total_amount_received=VALUES(total_amount_received), 
                status=VALUES(status), webhook_url=VALUES(webhook_url), reset_token=VALUES(reset_token), reset_expires=VALUES(reset_expires), support_link=VALUES(support_link)";
                
    $stmt = getDB()->prepare($sql);
    
    $apiKeys = isset($userData['api_keys']) && is_array($userData['api_keys']) 
                ? json_encode($userData['api_keys']) 
                : (isset($userData['api_keys']) ? $userData['api_keys'] : '[]');

    $stmt->execute([
        'id' => $userData['id'],
        'name' => $userData['name'],
        'email' => $userData['email'],
        'phone' => $userData['phone'] ?? '',
        'password_hash' => $userData['password_hash'],
        'created_at' => $userData['created_at'],
        'fampay_gmail' => $userData['fampay_gmail'] ?? '',
        'fampay_app_password_encrypted' => $userData['fampay_app_password_encrypted'] ?? '',
        'fampay_upi_id' => $userData['fampay_upi_id'] ?? '',
        'gmail_connected' => !empty($userData['gmail_connected']) ? 1 : 0,
        'google_linked' => !empty($userData['google_linked']) ? 1 : 0,
        'api_keys' => $apiKeys,
        'reset_chances' => $userData['reset_chances'] ?? 100,
        'avatar' => $userData['avatar'] ?? '',
        'plan_id' => $userData['plan_id'] ?? 'trial',
        'plan_label' => $userData['plan_label'] ?? '1 Day Free Trial',
        'plan_expires_at' => $userData['plan_expires_at'] ?? null,
        'total_requests' => $userData['total_requests'] ?? 0,
        'total_success' => $userData['total_success'] ?? 0,
        'total_failed' => $userData['total_failed'] ?? 0,
        'total_amount_received' => $userData['total_amount_received'] ?? 0.00,
        'status' => $userData['status'] ?? 'active',
        'webhook_url' => $userData['webhook_url'] ?? '',
        'reset_token' => $userData['reset_token'] ?? null,
        'reset_expires' => $userData['reset_expires'] ?? null,
        'support_link' => $userData['support_link'] ?? ''
    ]);
    return true;
}

// ==========================================================


function generateUserId() {
    return 'USR' . strtoupper(substr(md5(uniqid(mt_rand(), true)), 0, 10));
}

function generateApiKey() {
    return 'fam_' . bin2hex(random_bytes(20));
}

function generateCustomApiKey($rawName) {
    $clean = preg_replace('/[^A-Za-z0-9]/', '', $rawName);
    if ($clean === '') $clean = 'user';
    $digits = str_pad((string) random_int(0, 9999), 4, '0', STR_PAD_LEFT);
    return $clean . '_fampay' . $digits;
}

function getAllUsers() {
    $stmt = getDB()->query("SELECT * FROM users");
    $users = $stmt->fetchAll();
    return array_map('migrateUserSchema', $users);
}

function getUserByApiKey($apiKey) {
    if (empty($apiKey)) return null;
    // JSON_CONTAINS is index-friendly on MySQL 5.7+; LIKE fallback for partial match
    $stmt = getDB()->prepare(
        "SELECT * FROM users WHERE JSON_SEARCH(api_keys, 'one', ?, NULL, '$[*].key') IS NOT NULL"
    );
    $stmt->execute([$apiKey]);
    $users = $stmt->fetchAll();
    if (!$users) {
        // Fallback for legacy single api_key column
        $stmt2 = getDB()->prepare("SELECT * FROM users WHERE api_keys LIKE ?");
        $stmt2->execute(['%' . $apiKey . '%']);
        $users = $stmt2->fetchAll();
    }
    foreach ($users as $user) {
        $user = migrateUserSchema($user);
        foreach ($user['api_keys'] as $k) {
            if (($k['key'] ?? '') === $apiKey) return $user;
        }
    }
    return null;
}

// ==========================================================
// SESSION / AUTH HELPERS
// ==========================================================
function startSecureSession() {
    if (session_status() === PHP_SESSION_NONE) {
        session_set_cookie_params([
            'lifetime' => 7 * 24 * 60 * 60,
            'path' => '/',
            'secure' => isset($_SERVER['HTTPS']),
            'httponly' => true,
            'samesite' => 'Lax'
        ]);
        session_start();
    }

    // Enforce 24-hour inactivity timeout for regular user sessions
    // (Admin sessions are manual-logout-only by design)
    if (isset($_SESSION['user_id']) && !isset($_SESSION['is_admin'])) {
        $timeout = 24 * 60 * 60; // 24 hours
        if (isset($_COOKIE['remember_token'])) {
            $timeout = 7 * 24 * 60 * 60; // 7 days
        }
        if (isset($_SESSION['_last_activity']) && (time() - $_SESSION['_last_activity']) > $timeout) {
            session_unset();
            session_destroy();
            $isAjax = (!empty($_SERVER['HTTP_X_REQUESTED_WITH']) && strtolower($_SERVER['HTTP_X_REQUESTED_WITH']) == 'xmlhttprequest');
            $isApi = (strpos($_SERVER['REQUEST_URI'] ?? '', '/api/') !== false);
            if ($isAjax || $isApi) {
                http_response_code(401);
                header('Content-Type: application/json');
                echo json_encode(['status' => 'unauthorized', 'message' => 'Session expired. Please log in again.']);
                exit;
            }
            header('Location: login.php?expired=1');
            setcookie('remember_token', '', time() - 3600, '/');
            exit;
        }
        $_SESSION['_last_activity'] = time();
    }
}

// Generate (or reuse) a per-session CSRF token
function csrfToken() {
    if (empty($_SESSION['csrf_token'])) {
        $_SESSION['csrf_token'] = bin2hex(random_bytes(32));
    }
    return $_SESSION['csrf_token'];
}

function csrfVerify() {
    $submitted = $_POST['csrf_token'] ?? '';
    $expected  = $_SESSION['csrf_token'] ?? '';
    if (!$submitted || !$expected || !hash_equals($expected, $submitted)) {
        if (isset($_SERVER['HTTP_ACCEPT']) && strpos($_SERVER['HTTP_ACCEPT'], 'application/json') !== false) {
            http_response_code(200);
            echo json_encode([
                'status' => 'error', 
                'message' => 'Security check failed. Please refresh the page and try again. ' . ($expected ? 'Token mismatch.' : 'Session lost.')
            ]);
            exit;
        }
        http_response_code(403);
        exit('Invalid CSRF token. Please go back and try again.');
    }
}

function isLoggedIn() {
    if (isset($_SESSION['user_id'])) return true;
    if (isset($_COOKIE['remember_token'])) {
        $tokenData = decryptData($_COOKIE['remember_token']);
        if ($tokenData) {
            $data = json_decode($tokenData, true);
            if ($data && isset($data['id']) && isset($data['time'])) {
                if (time() - $data['time'] <= 7 * 24 * 60 * 60) {
                    $_SESSION['user_id'] = $data['id'];
                    $_SESSION['_last_activity'] = time();
                    return true;
                } else {
                    setcookie('remember_token', '', time() - 3600, '/');
                }
            }
        }
    }
    return false;
}

function isAdminLoggedIn() {
    if (isset($_SESSION['is_admin']) && $_SESSION['is_admin'] === true) return true;
    if (isset($_COOKIE['remember_token'])) {
        $tokenData = decryptData($_COOKIE['remember_token']);
        if ($tokenData) {
            $data = json_decode($tokenData, true);
            if ($data && !empty($data['admin']) && isset($data['time'])) {
                if (time() - $data['time'] <= 7 * 24 * 60 * 60) {
                    $_SESSION['is_admin'] = true;
                    $_SESSION['_last_activity'] = time();
                    return true;
                } else {
                    setcookie('remember_token', '', time() - 3600, '/');
                }
            }
        }
    }
    return false;
}

function requireLogin() {
    if (!isLoggedIn()) {
        header('Location: /login.php');
        exit;
    }
}

function requireAdmin() {
    if (!isAdminLoggedIn()) {
        header('Location: /login.php');
        exit;
    }
}


// ==========================================================
// ADMIN SETTINGS HELPERS
// ==========================================================
function getAdminSettings() {
    static $columnsChecked = false;
    if (!$columnsChecked) {
        $columnsChecked = true;
        try { getDB()->exec("ALTER TABLE admin_settings ADD COLUMN allow_registration TINYINT(1) NOT NULL DEFAULT 1"); } catch (\Exception $e) {}
        try { getDB()->exec("ALTER TABLE admin_settings ADD COLUMN allow_login TINYINT(1) NOT NULL DEFAULT 1"); } catch (\Exception $e) {}
        try { getDB()->exec("ALTER TABLE admin_settings ADD COLUMN smtp_host VARCHAR(255) DEFAULT ''"); } catch (\Exception $e) {}
        try { getDB()->exec("ALTER TABLE admin_settings ADD COLUMN smtp_port INT DEFAULT 587"); } catch (\Exception $e) {}
        try { getDB()->exec("ALTER TABLE admin_settings ADD COLUMN smtp_user VARCHAR(255) DEFAULT ''"); } catch (\Exception $e) {}
        try { getDB()->exec("ALTER TABLE admin_settings ADD COLUMN smtp_pass_encrypted VARCHAR(255) DEFAULT ''"); } catch (\Exception $e) {}
        try { getDB()->exec("ALTER TABLE admin_settings ADD COLUMN smtp_encryption VARCHAR(50) DEFAULT 'tls'"); } catch (\Exception $e) {}
        try { getDB()->exec("ALTER TABLE admin_settings ADD COLUMN smtp_from_email VARCHAR(255) DEFAULT ''"); } catch (\Exception $e) {}
        try { getDB()->exec("ALTER TABLE admin_settings ADD COLUMN smtp_from_name VARCHAR(255) DEFAULT ''"); } catch (\Exception $e) {}
        try { getDB()->exec("ALTER TABLE admin_settings ADD COLUMN enable_logging TINYINT(1) NOT NULL DEFAULT 1"); } catch (\Exception $e) {}
        try {
            getDB()->exec("CREATE TABLE IF NOT EXISTS `system_logs` (
                `id` INT AUTO_INCREMENT PRIMARY KEY,
                `created_at` DATETIME NOT NULL,
                `source` VARCHAR(50) NOT NULL,
                `message` TEXT NOT NULL
            )");
        } catch (\Exception $e) {}
    }
    $stmt = getDB()->query("SELECT * FROM admin_settings WHERE id = 1");
    $settings = $stmt->fetch();
    if (!$settings) {
        return [
            'gmail_id' => '', 'app_password_encrypted' => '', 'upi_id' => '',
            'custom_api_price' => 100, 'reset_unlock_price' => 5, 'base_url' => '', 'demo_amount' => 1,
            'allow_registration' => 1, 'allow_login' => 1,
            'smtp_host' => '', 'smtp_port' => 587, 'smtp_user' => '', 'smtp_pass_encrypted' => '',
            'smtp_encryption' => 'tls', 'smtp_from_email' => '', 'smtp_from_name' => '', 'enable_logging' => 1
        ];
    }
    if (!isset($settings['allow_registration'])) { $settings['allow_registration'] = 1; }
    if (!isset($settings['allow_login'])) { $settings['allow_login'] = 1; }
    if (!isset($settings['smtp_host'])) { $settings['smtp_host'] = ''; }
    if (!isset($settings['smtp_port'])) { $settings['smtp_port'] = 587; }
    if (!isset($settings['smtp_user'])) { $settings['smtp_user'] = ''; }
    if (!isset($settings['smtp_pass_encrypted'])) { $settings['smtp_pass_encrypted'] = ''; }
    if (!isset($settings['smtp_encryption'])) { $settings['smtp_encryption'] = 'tls'; }
    if (!isset($settings['smtp_from_email'])) { $settings['smtp_from_email'] = ''; }
    if (!isset($settings['smtp_from_name'])) { $settings['smtp_from_name'] = ''; }
    if (!isset($settings['enable_logging'])) { $settings['enable_logging'] = 1; }
    return $settings;
}

function addSystemLog($source, $message) {
    $settings = getAdminSettings();
    if (empty($settings['enable_logging'])) return;
    try {
        $stmt = getDB()->prepare("INSERT INTO system_logs (created_at, source, message) VALUES (?, ?, ?)");
        $stmt->execute([nowIST()->format('Y-m-d H:i:s'), $source, $message]);
    } catch (\Exception $e) {}
}

function getBaseUrl() {
    $settings = getAdminSettings();
    if (!empty($settings['base_url'])) {
        return rtrim($settings['base_url'], '/');
    }
    $scheme = (!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off') ? 'https' : 'http';
    $host = $_SERVER['HTTP_HOST'] ?? 'localhost';
    return $scheme . '://' . $host;
}

function saveAdminSettings($gmailId, $appPassword, $upiId) {
    $enc = encryptData($appPassword);
    $stmt = getDB()->prepare("UPDATE admin_settings SET gmail_id=?, app_password_encrypted=?, upi_id=? WHERE id=1");
    $stmt->execute([$gmailId, $enc, $upiId]);
    return true;
}

function updateAdminSettings($updates) {
    if (empty($updates)) return true;
    $setParts = [];
    $params = [];
    foreach ($updates as $k => $v) {
        $setParts[] = "`$k` = :$k";
        $params[$k] = $v;
    }
    $sql = "UPDATE admin_settings SET " . implode(', ', $setParts) . " WHERE id = 1";
    $stmt = getDB()->prepare($sql);
    $stmt->execute($params);
    return true;
}

// ==========================================================
// MISC HELPERS
// ==========================================================
function jsonResponse($data, $httpCode = 200) {
    http_response_code($httpCode);
    header('Content-Type: application/json');
    echo json_encode($data, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE);
    exit;
}

function nowIST() {
    return new DateTime('now', new DateTimeZone('Asia/Kolkata'));
}

// ==========================================================
// ORDER HELPERS
// ==========================================================
function saveOrder($order) {

    $sql = "INSERT INTO orders (
        order_id, type, user_id, upi_id, amount, payable_amount, status, created_at, 
        created_at_timestamp, expires_at_timestamp, transaction_id, utr, sender_name, payment_time_ist, redirect_url, webhook_url
    ) VALUES (
        :order_id, :type, :user_id, :upi_id, :amount, :payable_amount, :status, :created_at, 
        :created_at_timestamp, :expires_at_timestamp, :transaction_id, :utr, :sender_name, :payment_time_ist, :redirect_url, :webhook_url
    )";
    $stmt = getDB()->prepare($sql);
    $stmt->execute([
        'order_id' => $order['order_id'],
        'type' => $order['type'] ?? 'api_order',
        'user_id' => $order['user_id'],
        'upi_id' => $order['upi_id'] ?? '',
        'amount' => $order['amount'],
        'payable_amount' => $order['payable_amount'] ?? $order['amount'],
        'status' => $order['status'] ?? 'pending',
        'created_at' => $order['created_at'],
        'created_at_timestamp' => $order['created_at_timestamp'],
        'expires_at_timestamp' => $order['expires_at_timestamp'],
        'transaction_id' => $order['transaction_id'] ?? '',
        'utr' => $order['utr'] ?? '',
        'sender_name' => $order['sender_name'] ?? '',
        'payment_time_ist' => $order['payment_time_ist'] ?? '',
        'redirect_url' => $order['redirect_url'] ?? '',
        'webhook_url' => $order['webhook_url'] ?? ''
    ]);
}

/**
 * Fire a signed webhook to the merchant's webhook URL.
 * Uses HMAC-SHA256 signature (industry standard, same as Stripe/Razorpay).
 * Runs with a 5s cURL timeout so it never blocks the main request thread.
 */
function fireWebhook($orderId) {
    $order = getOrderById($orderId);
    if (!$order) return;

    $user = getUserById($order['user_id']);
    if (!$user) return;

    // Order-level webhook_url takes priority; fallback to global account webhook
    $webhookUrl = trim($order['webhook_url'] ?? '');
    if (empty($webhookUrl)) {
        $webhookUrl = trim($user['webhook_url'] ?? '');
    }
    if (empty($webhookUrl)) return;

    // Build the payload
    $payload = json_encode([
        'event'          => 'payment.success',
        'order_id'       => $order['order_id'],
        'amount'         => $order['amount'],
        'payable_amount' => $order['payable_amount'] ?? $order['amount'],
        'status'         => 'success',
        'transaction_id' => $order['transaction_id'] ?? '',
        'utr'            => $order['utr'] ?? '',
        'sender_name'    => $order['sender_name'] ?? '',
        'payment_time'   => $order['payment_time_ist'] ?? '',
        'timestamp'      => time()
    ]);

    // Sign the payload using merchant's API key as secret (HMAC SHA256)
    $apiKey = '';
    if (!empty($user['api_keys']) && is_array($user['api_keys']) && count($user['api_keys']) > 0) {
        $apiKey = $user['api_keys'][0]['key'] ?? '';
    }
    $signature = hash_hmac('sha256', $payload, $apiKey);

    initWebhookJobsTable();
    $sql = "INSERT INTO webhook_jobs (user_id, webhook_url, payload, signature, status, created_at) VALUES (?, ?, ?, ?, 'pending', ?)";
    $stmt = getDB()->prepare($sql);
    $stmt->execute([
        $user['id'],
        $webhookUrl,
        $payload,
        $signature,
        nowIST()->format('Y-m-d H:i:s')
    ]);
}



function updateOrder($orderId, $updates) {
    if (empty($updates)) return true;
    
    $setParts = [];
    $params = ['order_id' => $orderId];
    foreach ($updates as $k => $v) {
        $setParts[] = "`$k` = :$k";
        $params[$k] = $v;
    }
    $sql = "UPDATE orders SET " . implode(', ', $setParts) . " WHERE order_id = :order_id";
    $stmt = getDB()->prepare($sql);
    $stmt->execute($params);
    return true;
}

function getOrderById($orderId) {
    $stmt = getDB()->prepare("SELECT * FROM orders WHERE order_id = ?");
    $stmt->execute([$orderId]);
    return $stmt->fetch() ?: null;
}

function getAllOrders() {
    return getDB()->query("SELECT * FROM orders")->fetchAll();
}

// Efficient admin dashboard stats — SQL aggregates, no full table load
function getAdminDashboardStats() {
    $db = getDB();
    $stats = $db->query(
        "SELECT
            COUNT(CASE WHEN status='success' AND type != 'demo_test' THEN 1 END) AS total_success,
            SUM(CASE WHEN status='success' AND type != 'demo_test' THEN amount ELSE 0 END) AS total_revenue,
            COUNT(CASE WHEN status='pending' AND type != 'demo_test' THEN 1 END) AS total_pending,
            COUNT(CASE WHEN status NOT IN ('success','pending') AND type != 'demo_test' THEN 1 END) AS total_failed,
            COUNT(CASE WHEN type != 'demo_test' THEN 1 END) AS total_orders
        FROM orders"
    )->fetch();
    $links = $db->query(
        "SELECT
            COUNT(CASE WHEN status='pending' AND expires_at > UNIX_TIMESTAMP() THEN 1 END) AS pending_links,
            COUNT(CASE WHEN status='expired' THEN 1 END) AS expired_links
        FROM payment_links"
    )->fetch();
    return [
        'total_success'  => (int)($stats['total_success'] ?? 0),
        'total_revenue'  => floatval($stats['total_revenue'] ?? 0),
        'total_pending'  => (int)($stats['total_pending'] ?? 0) + (int)($links['pending_links'] ?? 0),
        'total_failed'   => (int)($stats['total_failed'] ?? 0) + (int)($links['expired_links'] ?? 0),
        'total_orders'   => (int)($stats['total_orders'] ?? 0) + (int)($links['pending_links'] ?? 0) + (int)($links['expired_links'] ?? 0),
    ];
}

// Efficient per-user stats for admin/users.php — one query for all users
function getAllUserOrderStats() {
    $db = getDB();
    $rows = $db->query(
        "SELECT user_id,
            SUM(CASE WHEN status='success' THEN amount ELSE 0 END) AS paid,
            SUM(CASE WHEN status='pending' THEN amount ELSE 0 END) AS pending,
            SUM(CASE WHEN status NOT IN ('success','pending') THEN amount ELSE 0 END) AS failed
        FROM orders GROUP BY user_id"
    )->fetchAll();
    $stats = [];
    foreach ($rows as $r) $stats[$r['user_id']] = $r;
    // Include pending/expired payment links
    $linkRows = $db->query(
        "SELECT user_id,
            SUM(CASE WHEN status='pending' AND expires_at > UNIX_TIMESTAMP() THEN amount ELSE 0 END) AS pending,
            SUM(CASE WHEN status='expired' THEN amount ELSE 0 END) AS failed
        FROM payment_links GROUP BY user_id"
    )->fetchAll();
    foreach ($linkRows as $r) {
        $uid = $r['user_id'];
        if (!isset($stats[$uid])) $stats[$uid] = ['paid'=>0,'pending'=>0,'failed'=>0];
        $stats[$uid]['pending'] = ($stats[$uid]['pending'] ?? 0) + $r['pending'];
        $stats[$uid]['failed']  = ($stats[$uid]['failed']  ?? 0) + $r['failed'];
    }
    return $stats;
}

// Efficient recent orders for admin — last N rows only
function getRecentOrders($limit = 10) {
    $stmt = getDB()->prepare("SELECT * FROM orders ORDER BY created_at_timestamp DESC LIMIT ?");
    $stmt->execute([$limit]);
    return $stmt->fetchAll();
}

function generateOrderId($prefix = 'fg_') {
    // Generate 8 random alphanumeric characters (A-Z, 0-9)
    $characters = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ';
    $random = '';
    for ($i = 0; $i < 8; $i++) {
        $random .= $characters[random_int(0, strlen($characters) - 1)];
    }
    return $prefix . $random;
}

/**
 * Assign a unique payable amount to prevent collisions.
 * E.g., if two orders are for ₹150, they get ₹150.01 and ₹150.02
 * so IMAP matching is 100% unambiguous.
 */
function getUniquePayableAmount($baseAmount, $userId) {
    $base = floatval($baseAmount);
    // Look for any active pending orders/links from this merchant with same base amount
    $stmt = getDB()->prepare(
        "SELECT payable_amount FROM orders 
         WHERE user_id = ? AND amount = ? AND status = 'pending' 
         AND expires_at_timestamp > ?
         UNION ALL
         SELECT payable_amount FROM payment_links 
         WHERE user_id = ? AND amount = ? AND status = 'pending' 
         AND expires_at > ?"
    );
    $now = time();
    $stmt->execute([$userId, $base, $now, $userId, $base, $now]);
    $taken = array_column($stmt->fetchAll(), 'payable_amount');
    $taken = array_map('floatval', $taken);

    // Find the first available cent from .01 to .99
    for ($cents = 1; $cents <= 99; $cents++) {
        $candidate = round($base + $cents / 100, 2);
        if (!in_array($candidate, $taken)) {
            return $candidate;
        }
    }
    // Fallback: flat base amount (unlikely to hit)
    return $base;
}

/**
 * DB-based IMAP lock: ensures we don't hit Gmail more than once per N seconds per merchant.
 * Returns true if lock was acquired (safe to do IMAP call), false if still locked.
 */
function acquireImapLock($userId, $lockSeconds = 3) {
    $db = getDB();
    $now = time();

    // Upsert lock row; only update if expired
    $stmt = $db->prepare(
        "INSERT INTO imap_locks (user_id, locked_at) VALUES (?, ?)
         ON DUPLICATE KEY UPDATE locked_at = IF(locked_at < ?, VALUES(locked_at), locked_at)"
    );
    $stmt->execute([$userId, $now, $now - $lockSeconds]);

    // Read back what's stored
    $row = $db->prepare("SELECT locked_at FROM imap_locks WHERE user_id = ?");
    $row->execute([$userId]);
    $locked_at = (int)($row->fetchColumn() ?: 0);

    return $locked_at === $now; // We set it to now = we got the lock
}

// ==========================================================
// FAMAPP GMAIL VERIFICATION CORE (IMAP-based)
// Shared by: dashboard plan-purchase checker AND public API /verify_order
// ==========================================================
function connectFamAppGmail($gmailId, $appPassword) {
    if (!function_exists('imap_open')) {
        return ['success' => false, 'error' => 'IMAP extension not enabled on server.'];
    }

    // Cut connect/read/write timeouts down (default can be 30s+) so a slow
    // network hop doesn't stall the whole request. 8s is generous for a
    // TLS+login round trip to Gmail but won't let it hang.
    @imap_timeout(IMAP_OPENTIMEOUT, 8);
    @imap_timeout(IMAP_READTIMEOUT, 8);
    @imap_timeout(IMAP_WRITETIMEOUT, 8);

    $inbox = @imap_open('{imap.gmail.com:993/imap/ssl/novalidate-cert}INBOX', $gmailId, $appPassword, 0, 1);
    if (!$inbox) {
        $error = imap_last_error();
        // Clear IMAP errors to avoid pollution
        imap_errors();
        
        // If we still somehow get a cert error despite novalidate-cert, it often masks a bad password
        if (strpos($error, 'Certificate failure') !== false || strpos($error, 'SNI') !== false) {
            $error = "Check your App Password (it should be 16 letters with no spaces).";
        }
        
        return ['success' => false, 'error' => 'Could not connect to Gmail: ' . $error];
    }
    return ['success' => true, 'inbox' => $inbox];
}

function getEmailBodyDecoded($inbox, $uid) {
    $structure = @imap_fetchstructure($inbox, $uid, FT_UID);

    $message = @imap_fetchbody($inbox, $uid, '1', FT_UID);
    if (empty(trim(strip_tags($message)))) {
        $message = @imap_fetchbody($inbox, $uid, '1.1', FT_UID);
    }
    if (empty(trim(strip_tags($message)))) {
        $message = @imap_body($inbox, $uid, FT_UID);
    }

    $partEncoding = null;
    if (isset($structure->parts) && is_array($structure->parts) && count($structure->parts) > 0) {
        $partEncoding = findTextPartEncodingRecursive($structure->parts);
    } elseif (isset($structure->encoding)) {
        $partEncoding = $structure->encoding;
    }

    if ($partEncoding == 3) {
        $decoded = base64_decode($message);
        if ($decoded !== false) $message = $decoded;
    } elseif ($partEncoding == 4) {
        $message = quoted_printable_decode($message);
    }

    // Safety net: force-decode if literal =XX hex escapes are still present
    if (preg_match('/=[0-9A-Fa-f]{2}/', $message) || strpos($message, "=\r\n") !== false || strpos($message, "=\n") !== false) {
        $message = quoted_printable_decode($message);
    }

    return strip_tags($message);
}

function findTextPartEncodingRecursive($parts) {
    foreach ($parts as $part) {
        if (isset($part->subtype) && (strtoupper($part->subtype) === 'PLAIN' || strtoupper($part->subtype) === 'HTML')) {
            return $part->encoding ?? null;
        }
        if (isset($part->parts) && is_array($part->parts)) {
            $found = findTextPartEncodingRecursive($part->parts);
            if ($found !== null) return $found;
        }
    }
    return null;
}

/**
 * Check a connected Gmail inbox for a FamPay payment matching $expectedAmount,
 * received AFTER $sinceTimestamp. Returns match details or pending status.
 */
function checkFamAppInboxForPayment($gmailId, $appPassword, $expectedAmount, $sinceTimestamp, $orderId = '') {
    $conn = connectFamAppGmail($gmailId, $appPassword);
    if (!$conn['success']) {
        return ['status' => 'error', 'message' => $conn['error']];
    }
    $inbox = $conn['inbox'];

    // Narrow the search to only emails since the order was created (minus 1 day
    // buffer for IMAP's date-only SINCE granularity). This avoids Gmail scanning
    // and returning the whole historical match set on every single poll.
    $sinceDate = $sinceTimestamp > 0
        ? date('d-M-Y', $sinceTimestamp - 86400)
        : date('d-M-Y', time() - 86400);
    $emails = imap_search($inbox, 'FROM "no-reply@famapp.in" SINCE "' . $sinceDate . '"', SE_UID);
    if (!$emails) {
        imap_close($inbox);
        return ['status' => 'pending', 'message' => 'No FamPay emails found yet.'];
    }

    rsort($emails);
    $checkCount = 0;

    foreach ($emails as $uid) {
        // Since results are now already narrowed to "since order creation",
        // checking the 4 most recent is enough and cuts fetch/parse time
        // versus the old fixed limit of 10.
        if ($checkCount >= 8) break;
        $checkCount++;

        $headerInfo = @imap_headerinfo($inbox, imap_msgno($inbox, $uid));
        $emailTimestamp = ($headerInfo && isset($headerInfo->udate)) ? $headerInfo->udate : 0;

        // Skip emails older than when this order/QR was generated (60s buffer for clock skew)
        if ($sinceTimestamp > 0 && $emailTimestamp > 0 && $emailTimestamp < ($sinceTimestamp - 60)) {
            continue;
        }

        $body = getEmailBodyDecoded($inbox, $uid);

        // Match 'received ₹1.01' or 'received 1.01'
        if (preg_match('/received[^0-9]{0,15}?([\\d,]+\\.?\\d*)\\s*from/iu', str_replace('₹', '', $body), $matches)) {
            $foundAmount = floatval(str_replace(',', '', $matches[1]));
            $expected = floatval($expectedAmount);

            if (abs($foundAmount - $expected) < 0.01) {
                $txnId = '';
                $utr = '';
                $senderName = '';
                if (preg_match('/transaction id\s*:?\s*([A-Za-z0-9]+)/i', $body, $m)) $txnId = $m[1];
                if (preg_match('/UTR\s*:?\s*(\d+)/i', $body, $m)) $utr = $m[1];
                if (preg_match('/from\s+([A-Z\s]+?)\s+at\s+\d{1,2}:\d{2}/i', $body, $m)) $senderName = trim($m[1]);

                // --- DOUBLE-SPEND FIX ---
                // Verify this transaction hasn't already been claimed by another successful order
                if (!empty($txnId) && !empty($orderId)) {
                    $stmt = getDB()->prepare("SELECT order_id FROM orders WHERE transaction_id = ? AND status = 'success' AND order_id != ?");
                    $stmt->execute([$txnId, $orderId]);
                    if ($stmt->fetch()) {
                        // This email was already used to confirm another order. Skip it.
                        continue;
                    }
                }

                imap_close($inbox);
                return [
                    'status' => 'success',
                    'amount' => $foundAmount,
                    'transaction_id' => $txnId,
                    'utr' => $utr,
                    'sender_name' => $senderName,
                    'payment_timestamp' => $emailTimestamp
                ];
            }
        }
    }

    imap_close($inbox);
    return ['status' => 'pending', 'message' => 'Payment not received yet.'];
}

/**
 * Fetch all recent FamPay payments from the inbox. Used by cron job for bulk matching.
 */
function getFamAppInboxPayments($gmailId, $appPassword) {
    $conn = connectFamAppGmail($gmailId, $appPassword);
    if (!$conn['success']) {
        return [];
    }
    $inbox = $conn['inbox'];
    
    $sinceDate = date('d-M-Y', time() - 86400);
    $emails = imap_search($inbox, 'FROM "no-reply@famapp.in" SINCE "' . $sinceDate . '"', SE_UID);
    if (!$emails) {
        imap_close($inbox);
        return [];
    }
    
    rsort($emails);
    $payments = [];
    $checkCount = 0;
    
    foreach ($emails as $uid) {
        if ($checkCount >= 15) break; // Fetch up to 15 recent emails for batch processing
        $checkCount++;
        
        $headerInfo = @imap_headerinfo($inbox, imap_msgno($inbox, $uid));
        $emailTimestamp = ($headerInfo && isset($headerInfo->udate)) ? $headerInfo->udate : 0;
        
        $body = getEmailBodyDecoded($inbox, $uid);
        
        if (preg_match('/received[^0-9]{0,15}?([\d,]+\.?\d*)\s*from/iu', str_replace('₹', '', $body), $matches)) {
            $foundAmount = floatval(str_replace(',', '', $matches[1]));
            $txnId = '';
            $utr = '';
            $senderName = '';
            if (preg_match('/transaction id\s*:?\s*([A-Za-z0-9]+)/i', $body, $m)) $txnId = $m[1];
            if (preg_match('/UTR\s*:?\s*(\d+)/i', $body, $m)) $utr = $m[1];
            if (preg_match('/from\s+([A-Z\s]+?)\s+at\s+\d{1,2}:\d{2}/i', $body, $m)) $senderName = trim($m[1]);
            
            $payments[] = [
                'amount' => $foundAmount,
                'transaction_id' => $txnId,
                'utr' => $utr,
                'sender_name' => $senderName,
                'payment_timestamp' => $emailTimestamp
            ];
        }
    }
    imap_close($inbox);
    return $payments;
}


// ==========================================================
// PAYMENT LINKS HELPERS
// ==========================================================

function createPaymentLink($userId, $amount, $slug, $expiresAt) {
    if (empty($slug)) {
        // Use the new standard order ID format for the slug as well for consistency
        $slug = generateOrderId(); // Uses default 'fg_' prefix
    }
    // Check if slug exists
    $stmt = getDB()->prepare("SELECT slug FROM payment_links WHERE slug = ?");
    $stmt->execute([$slug]);
    if ($stmt->fetch()) {
        return ['success' => false, 'error' => 'URL Slug already exists. Please choose another.'];
    }

    // Generate a unique payable amount for this link to ensure reliable IMAP matching
    $db = getDB();
    $db->beginTransaction();
    try {
        $stmtLock = $db->prepare("SELECT id FROM users WHERE id = ? FOR UPDATE");
        $stmtLock->execute([$userId]);
        
        $payableAmount = getUniquePayableAmount($amount, $userId);

        $stmt = $db->prepare("INSERT INTO payment_links (slug, user_id, amount, payable_amount, status, created_at, expires_at) VALUES (?, ?, ?, ?, 'pending', ?, ?)");
        $stmt->execute([$slug, $userId, $amount, $payableAmount, time(), $expiresAt]);
        $db->commit();
    } catch (\Exception $e) {
        $db->rollBack();
        return ['success' => false, 'error' => 'Database error. Please try again.'];
    }

    return ['success' => true, 'slug' => $slug];
}

function getAllPaymentLinks() {
    return getDB()->query("SELECT * FROM payment_links")->fetchAll();
}

function getPaymentLinksByUser($userId) {
    $stmt = getDB()->prepare("SELECT * FROM payment_links WHERE user_id = ? ORDER BY created_at DESC");
    $stmt->execute([$userId]);
    return $stmt->fetchAll();
}

function getPaymentLinkBySlug($slug) {
    $stmt = getDB()->prepare("SELECT * FROM payment_links WHERE slug = ?");
    $stmt->execute([$slug]);
    return $stmt->fetch();
}

function updatePaymentLink($slug, $status) {
    $stmt = getDB()->prepare("UPDATE payment_links SET status = ? WHERE slug = ?");
    $stmt->execute([$status, $slug]);
}

function reactivatePaymentLink($slug, $userId, $newExpiresAt = null) {
    $link = getPaymentLinkBySlug($slug);
    if (!$link || $link['user_id'] !== $userId) {
        return ['success' => false, 'error' => 'Link not found.'];
    }
    if ($newExpiresAt === null || $newExpiresAt <= time()) {
        $originalDuration = max(60, ($link['expires_at'] ?? 0) - ($link['created_at'] ?? 0));
        if ($originalDuration <= 0) { $originalDuration = 86400; }
        $newExpiresAt = time() + $originalDuration;
    }
    $db = getDB();
    $db->beginTransaction();
    try {
        $stmtLock = $db->prepare("SELECT id FROM users WHERE id = ? FOR UPDATE");
        $stmtLock->execute([$userId]);
        
        $payableAmount = getUniquePayableAmount($link['amount'], $userId);
        
        $now = time();
        $stmt = $db->prepare("UPDATE payment_links SET status = 'pending', expires_at = ?, payable_amount = ?, created_at = ? WHERE slug = ? AND user_id = ?");
        $stmt->execute([$newExpiresAt, $payableAmount, $now, $slug, $userId]);
        $db->commit();
        return ['success' => true];
    } catch (\Exception $e) {
        $db->rollBack();
        return ['success' => false, 'error' => 'Database error.'];
    }
}

function deletePaymentLink($slug, $userId) {
    $stmt = getDB()->prepare("DELETE FROM payment_links WHERE slug = ? AND user_id = ?");
    $stmt->execute([$slug, $userId]);
}

// ==========================================================
// WEBHOOK LOGS
// ==========================================================

function initWebhookLogsTable() {
    static $initDone = false;
    if ($initDone) return;
    $initDone = true;
    
    $sql = "CREATE TABLE IF NOT EXISTS webhook_logs (
        id INT AUTO_INCREMENT PRIMARY KEY,
        user_id VARCHAR(64) NOT NULL,
        endpoint_url TEXT NOT NULL,
        payload TEXT NOT NULL,
        response_code INT DEFAULT 0,
        response_body TEXT,
        created_at DATETIME NOT NULL,
        INDEX idx_user_id (user_id)
    ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;";
    try { getDB()->exec($sql); } catch (\Exception $e) {}
}

function initWebhookJobsTable() {
    static $initDone = false;
    if ($initDone) return;
    $initDone = true;
    
    $sql = "CREATE TABLE IF NOT EXISTS webhook_jobs (
        id INT AUTO_INCREMENT PRIMARY KEY,
        user_id VARCHAR(64) NOT NULL,
        webhook_url TEXT NOT NULL,
        payload TEXT NOT NULL,
        signature VARCHAR(255) NOT NULL,
        status VARCHAR(20) DEFAULT 'pending',
        created_at DATETIME NOT NULL,
        processed_at DATETIME NULL,
        INDEX idx_status (status)
    ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;";
    try { getDB()->exec($sql); } catch (\Exception $e) {}
}

function saveWebhookLog($userId, $endpointUrl, $payload, $responseCode, $responseBody) {
    initWebhookLogsTable();
    $sql = "INSERT INTO webhook_logs (user_id, endpoint_url, payload, response_code, response_body, created_at)
            VALUES (?, ?, ?, ?, ?, ?)";
    $stmt = getDB()->prepare($sql);
    $stmt->execute([
        $userId,
        $endpointUrl,
        $payload,
        $responseCode,
        $responseBody,
        nowIST()->format('Y-m-d H:i:s')
    ]);
}

function getWebhookLogs($userId, $limit = 50) {
    initWebhookLogsTable();
    $sql = "SELECT * FROM webhook_logs WHERE user_id = ? ORDER BY created_at DESC LIMIT ?";
    $stmt = getDB()->prepare($sql);
    // PDO needs bindParam for LIMIT integer to avoid syntax errors in some strict modes, or just cast to int
    $stmt->bindValue(1, $userId, PDO::PARAM_STR);
    $stmt->bindValue(2, (int)$limit, PDO::PARAM_INT);
    $stmt->execute();
    return $stmt->fetchAll();
}

function sendSMTPMail($to, $subject, $body, $isHtml = true) {
    $settings = getAdminSettings();
    if (empty($settings['smtp_host'])) {
        // Fallback to PHP mail if SMTP is not configured
        $headers = "MIME-Version: 1.0\r\n";
        $headers .= "Content-type:text/" . ($isHtml ? 'html' : 'plain') . ";charset=UTF-8\r\n";
        $headers .= "From: FamGateway <no-reply@{$_SERVER['HTTP_HOST']}>\r\n";
        return @mail($to, $subject, $body, $headers);
    }
    
    require_once __DIR__ . '/PHPMailer/Exception.php';
    require_once __DIR__ . '/PHPMailer/PHPMailer.php';
    require_once __DIR__ . '/PHPMailer/SMTP.php';
    
    $mail = new \PHPMailer\PHPMailer\PHPMailer(true);
    try {
        $mail->isSMTP();
        $mail->Host       = $settings['smtp_host'];
        $mail->SMTPAuth   = !empty($settings['smtp_user']);
        if ($mail->SMTPAuth) {
            $mail->Username = $settings['smtp_user'];
            $mail->Password = decryptData($settings['smtp_pass_encrypted']);
        }
        if (!empty($settings['smtp_encryption']) && strtolower($settings['smtp_encryption']) !== 'none') {
            $mail->SMTPSecure = strtolower($settings['smtp_encryption']) === 'ssl' ? \PHPMailer\PHPMailer\PHPMailer::ENCRYPTION_SMTPS : \PHPMailer\PHPMailer\PHPMailer::ENCRYPTION_STARTTLS;
        } else {
            $mail->SMTPAutoTLS = false;
        }
        $mail->Port       = $settings['smtp_port'];
        
        $fromEmail = !empty($settings['smtp_from_email']) ? $settings['smtp_from_email'] : 'no-reply@' . $_SERVER['HTTP_HOST'];
        $fromName = !empty($settings['smtp_from_name']) ? $settings['smtp_from_name'] : 'FamGateway';
        $mail->setFrom($fromEmail, $fromName);
        $mail->addAddress($to);
        
        $mail->isHTML($isHtml);
        $mail->Subject = $subject;
        $mail->Body    = $body;
        if (!$isHtml) {
            $mail->AltBody = $body;
        }
        
        $mail->send();
        return true;
    } catch (Exception $e) {
        error_log("SMTP Error: {$mail->ErrorInfo}");
        return false;
    }
}

function initEmailTemplatesTable() {
    static $initDone = false;
    if ($initDone) return;
    $initDone = true;
    
    $sql = "CREATE TABLE IF NOT EXISTS email_templates (
        id INT AUTO_INCREMENT PRIMARY KEY,
        template_key VARCHAR(100) UNIQUE NOT NULL,
        subject VARCHAR(255) NOT NULL,
        body TEXT NOT NULL,
        variables VARCHAR(255) DEFAULT ''
    ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;";
    try { getDB()->exec($sql); } catch (\Exception $e) {}

    // Insert defaults if missing
    $db = getDB();
    $stmt = $db->query("SELECT COUNT(*) FROM email_templates");
    if ($stmt->fetchColumn() == 0) {
        $welcomeSubject = "Welcome to FamGateway!";
        $welcomeBody = "<h3>Hi {name},</h3><p>Welcome to FamGateway! Your account has been created successfully.</p><p>Login here: {login_url}</p><p>Best regards,<br>FamGateway Team</p>";
        $welcomeVars = "{name}, {email}, {login_url}";
        
        $resetSubject = "Password Reset Request";
        $resetBody = "<h3>Hi {name},</h3><p>You requested a password reset. Please click the link below to reset your password:</p><p><a href=\"{reset_url}\">Reset Password</a></p><p>If you did not request this, please ignore this email.</p>";
        $resetVars = "{name}, {email}, {reset_url}";

        $db->prepare("INSERT INTO email_templates (template_key, subject, body, variables) VALUES (?, ?, ?, ?)")
           ->execute(['welcome_email', $welcomeSubject, $welcomeBody, $welcomeVars]);
        $db->prepare("INSERT INTO email_templates (template_key, subject, body, variables) VALUES (?, ?, ?, ?)")
           ->execute(['password_reset', $resetSubject, $resetBody, $resetVars]);
    }
}

function getEmailTemplate($key) {
    initEmailTemplatesTable();
    $stmt = getDB()->prepare("SELECT * FROM email_templates WHERE template_key = ?");
    $stmt->execute([$key]);
    return $stmt->fetch();
}

function getAllEmailTemplates() {
    initEmailTemplatesTable();
    $stmt = getDB()->query("SELECT * FROM email_templates ORDER BY id ASC");
    return $stmt->fetchAll();
}

function updateEmailTemplate($key, $subject, $body) {
    initEmailTemplatesTable();
    $stmt = getDB()->prepare("UPDATE email_templates SET subject = ?, body = ? WHERE template_key = ?");
    $stmt->execute([$subject, $body, $key]);
    return true;
}

/**
 * Verify Cloudflare Turnstile token
 */
function verifyTurnstile($token) {
    if (empty($token)) return false;
    $secret = defined('TURNSTILE_SECRET_KEY') ? TURNSTILE_SECRET_KEY : '';
    if (empty($secret)) return true; // If turnstile is not configured, pass validation

    $ch = curl_init('https://challenges.cloudflare.com/turnstile/v0/siteverify');
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
    curl_setopt($ch, CURLOPT_POST, true);
    curl_setopt($ch, CURLOPT_POSTFIELDS, [
        'secret' => $secret,
        'response' => $token
    ]);
    $response = curl_exec($ch);
    curl_close($ch);

    if ($response) {
        $result = json_decode($response, true);
        return isset($result['success']) && $result['success'];
    }
    return false;
}

