<?php
require_once('/home/swapekic/secure/conf.php');

require 'vendor/autoload.php';

use PHPMailer\PHPMailer\PHPMailer;
use PHPMailer\PHPMailer\Exception;
$mail = new PHPMailer(true);

use GuzzleHttp\Client;
use GuzzleHttp\Exception\RequestException;
use Firebase\JWT\JWT;
$client = new Client();

//CONNECT
$mysqli = new mysqli(DB_HOST, DB_USER, DB_PASS, DB_NAME);

if( mysqli_connect_errno() ) 
{
    exit( mysqli_connect_error() );
}

$mysqli->set_charset("utf8mb4");


$protocol = (!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off' || $_SERVER['SERVER_PORT'] == 443) ? "https://" : "http://";
$domain = $_SERVER['HTTP_HOST'];
$baseURL = $protocol . $domain . '/';
$quidaxBase = "https://app.quidax.io/api/v1/";
$shBase = 'https://api.safehavenmfb.com/';
$qsk = "RpeROs3JyKXScB59xbZ3ffC11xQid2Jlk17xaMMx";

//VTPASS LIVE

 $vtpBase = "https://vtpass.com/api/";
 $vtpApiKey = "09e3f274b5553a71d0c043323747880a";
 $vtpPubKey = "PK_4866d69c3755685c57a31970f5653936abee7176bf7";
 $vtpSecKey = "SK_642fceb7a5f0dead6f6f0710d6cfe55dec91dc9b9cc";

//REPLACE THIS
$dojaBase = "https://api.dojah.io/";
$dojaApp = "696505292b10b0f779002694";
$dojaPub = "prod_pk_38XYaUeePwNwrwG7YczQ6AIZS";
$dojaSec = "prod_sk_KqmwyH2KUoVy0qHfSuih5EW73";

$nonChargeableFailureCodes = [
    '091', // TRANSACTION NOT PROCESSED – not processed and user will not be charged
    '016', // TRANSACTION FAILED
    '010', // VARIATION CODE DOES NOT EXIST
    '011', // INVALID ARGUMENTS
    '012', // PRODUCT DOES NOT EXIST
    '013', // BELOW MINIMUM AMOUNT ALLOWED
    '014', // REQUEST ID ALREADY EXIST
    '015', // INVALID REQUEST ID
    '017', // ABOVE MAXIMUM AMOUNT ALLOWED
    '018', // LOW WALLET BALANCE
    '019', // LIKELY DUPLICATE TRANSACTION
    '021', // ACCOUNT LOCKED
    '022', // ACCOUNT SUSPENDED
    '023', // API ACCESS NOT ENABLE FOR USER
    '024', // ACCOUNT INACTIVE
    '025', // RECIPIENT BANK INVALID
    '026', // RECIPIENT ACCOUNT COULD NOT BE VERIFIED
    '027', // IP NOT WHITELISTED, CONTACT SUPPORT
    '028', // PRODUCT IS NOT WHITELISTED ON YOUR ACCOUNT
    '030', // BILLER NOT REACHABLE AT THIS POINT
    '031', // BELOW MINIMUM QUANTITY ALLOWED
    '032', // ABOVE MINIMUM QUANTITY ALLOWED
    '034', // SERVICE SUSPENDED
    '035', // SERVICE INACTIVE
    '040', // TRANSACTION REVERSAL – implies refund
    '083', // SYSTEM ERROR
    '085', // IMPROPER REQUEST ID (general)
 
];

$uploadDir = __DIR__ . "/uploads/";
    $set = getSettings()[0];
    
      function getUsdEquivalent($currency, $cryptoAmount) {
    // Stablecoins
    if (in_array($currency, ['usdt', 'usdc'])) {
        return floatval($cryptoAmount);
    }

    if ($cryptoAmount <= 0) {
        return 0;
    }

    try {
        $q = $client->request('POST', $quidaxBase . 'users/me/swap_quotation', [
            'body' => json_encode([
                'from_currency' => $currency,
                'to_currency' => 'usdt',
                'from_amount' => $cryptoAmount,
            ]),
            'headers' => [
                'Authorization' => 'Bearer ' . $qsk,
                'Accept' => 'application/json',
                'Content-Type' => 'application/json',
            ],
        ]);

        $res = json_decode($q->getBody(), true);

        if (isset($res['status']) && $res['status'] === 'success') {
            return floatval($res['data']['to_amount'] ?? 0);
        }
    } catch (Exception $e) {
        error_log("USD conversion failed for {$currency}: " . $e->getMessage());
    }

    return 0;
}


function applyCashback(
    int $userId,
    string $transactionId,
    float $amountSpent
) {
    global $mysqli, $set;

    // ----------------------------
    // Lock user row (race-safe)
    // ----------------------------
    $stmt = $mysqli->prepare("
        SELECT email, first_name, cashback_tx_count, cashback_disabled
        FROM users
        WHERE id = ?
        FOR UPDATE
    ");
    $stmt->bind_param("i", $userId);
    $stmt->execute();
    $user = $stmt->get_result()->fetch_assoc();
    $stmt->close();

    if (!$user) return;

    // Hard stops
    if ((int)$user['cashback_disabled'] === 1) return;
    if ((int)$user['cashback_tx_count'] >= 5) return;

    // ----------------------------
    // Calculate cashback
    // ----------------------------
    $cashback = calculateCashback($amountSpent);
    if ($cashback <= 0) return;

    // ----------------------------
    // Prevent duplicate cashback
    // ----------------------------
    $check = $mysqli->prepare("
        SELECT id FROM cashback_logs WHERE transaction_id = ?
    ");
    $check->bind_param("s", $transactionId);
    $check->execute();
    if ($check->get_result()->num_rows > 0) {
        $check->close();
        return;
    }
    $check->close();

    // ----------------------------
    // Credit balance + increment counter
    // ----------------------------
    $credit = $mysqli->prepare("
        UPDATE users
        SET 
            ngn_balance = ngn_balance + ?, 
            cashback_tx_count = cashback_tx_count + 1
        WHERE id = ?
    ");
    $credit->bind_param("di", $cashback, $userId);
    $credit->execute();
    $credit->close();

    // ----------------------------
    // Log cashback
    // ----------------------------
    $log = $mysqli->prepare("
        INSERT INTO cashback_logs 
        (user_id, transaction_id, amount_spent, cashback_amount)
        VALUES (?, ?, ?, ?)
    ");
    $log->bind_param("isdd", $userId, $transactionId, $amountSpent, $cashback);
    $log->execute();
    $log->close();

    // ----------------------------
    // Record as transaction
    // ----------------------------
    $status  = 'Complete';
    $title   = 'Cashback Reward';
    $desc    = 'Cashback for eligible transaction';
    $time    = time();
    $our_ref = 'cb_' .  $userId . '_' . $time;
    $email   = $user['email'];
    $name    = $user['first_name'];
    $app     = $set['app_name'];
    $type    = 'cash_back';

    $insertTxn = $mysqli->prepare("
        INSERT INTO transactions 
        (flow, currency, title, description, date, amount, status, our_ref, email, user_id, type)
        VALUES ('in', 'NGN', ?, ?, ?, ?, ?, ?, ?, ?, ?)
    ");
    $insertTxn->bind_param(
        "sssdsssis",
        $title,
        $desc,
        $time,
        $cashback,
        $status,
        $our_ref,
        $email,
        $userId,
        $type
    );
    $insertTxn->execute();
    $insertTxn->close();

    // ----------------------------
    // 📧 EMAIL USER
    // ----------------------------
    $subject = "You've received ₦" . number_format($cashback) . " cashback!";

    $body = "
        <div style='font-family:Arial,sans-serif;max-width:600px;margin:auto;background:#f9f9f9;padding:20px;border-radius:8px;'>
            <h2 style='color:#1584d4;'>Cashback Reward</h2>

            <p>Hello <strong>{$name}</strong>,</p>

            <p>Great news! You just received a cashback reward on your recent transaction.</p>

            <table style='width:100%;margin:15px 0;border-collapse:collapse;'>
                <tr>
                    <td style='padding:8px;border-bottom:1px solid #ddd;'>Amount Spent</td>
                    <td style='padding:8px;border-bottom:1px solid #ddd;'><strong>₦" . number_format($amountSpent, 2) . "</strong></td>
                </tr>
                <tr>
                    <td style='padding:8px;'>Cashback Received</td>
                    <td style='padding:8px;'><strong>₦" . number_format($cashback, 2) . "</strong></td>
                </tr>
            </table>

            <p>The cashback has been credited instantly to your {$app} wallet.</p>

            <p style='font-size:13px;color:#666;margin-top:20px;'>
                This reward is part of our new user cashback promo (up to 5 rewards).
            </p>

            <hr>
            <p style='font-size:12px;color:#999;'>© " . date('Y') . " {$app}. All rights reserved.</p>
        </div>
    ";

    sendMail($email, $subject, $body);
      addNotification(
            $userId,
           $subject,
           'Cashback Received'
        );
}


function calculateCashback(float $amount): int
{
    $set = getSettings()[0];
    $ruleJson = $set['cashback_rules'];
$rule = json_decode($ruleJson, true);

    if (!isset($rule['tiers']) || !is_array($rule['tiers']) || !$rule['enabled']) {
        return 0;
    }

    // Sort tiers DESC by min_spend
    usort($rule['tiers'], fn($a, $b) => $b['min_spend'] <=> $a['min_spend']);

    foreach ($rule['tiers'] as $tier) {
        if ($amount >= $tier['min_spend']) {
            return (int)$tier['cashback'];
        }
    }

    return 0;
}

    
    function releaseActionLock($identifier, string $action): void
{
    global $mysqli;

    // If numeric → user_id lock
    if (is_int($identifier) || ctype_digit((string)$identifier)) {
        $userId = (int)$identifier;

        $stmt = $mysqli->prepare(
            "DELETE FROM action_locks WHERE user_id = ? AND action = ?"
        );
        $stmt->bind_param("is", $userId, $action);
        $stmt->execute();
        $stmt->close();
        return;
    }

    // Otherwise → fingerprint lock
    $fingerprint = (string)$identifier;

    $stmt = $mysqli->prepare(
        "DELETE FROM action_locks WHERE fingerprint = ? AND action = ?"
    );
    $stmt->bind_param("ss", $fingerprint, $action);
    $stmt->execute();
    $stmt->close();
}

function getAccessToken(){
    global $shBase, $client;
$url = $shBase . 'oauth2/token';

$data = [
    'grant_type' => 'client_credentials',
    'client_assertion_type' => 'urn:ietf:params:oauth:client-assertion-type:jwt-bearer',
    'client_assertion' => 'eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJodHRwczovL2FwcC5zd2FwZWtpLmNvbS8iLCJzdWIiOiJmZTk1NzM4NmI4ZjFhODAzY2ZlNmExZTY0NzJkZDljMyIsImF1ZCI6Imh0dHBzOi8vYXBpLnNhZmVoYXZlbm1mYi5jb20iLCJpYXQiOjE3NDM3MDk4NTYsImV4cCI6MjUzMjYyMTMzMX0.uky64j2QlfiSVkoKA5XB58nxbWqzQ7Dt-sudFnbbI8x_VTuWtM0U4NNSrHL6b_Wl4vuQyrC7Yp2WCWDonRE98532tWmvzbdzfGFGLOBI2KLJE2Wba2ynfaBI6jIgS3yN5teOpg557xuTkj3wyVU6grjJR2cd_aSGsmzVdTzE5FU',
    'client_id' => 'fe957386b8f1a803cfe6a1e6472dd9c3'
];

$headers = [
    'Accept' => 'application/json',
    'Content-Type' => 'application/json'
];


    $response = $client->request('POST', $url, [
        'json' => $data,  
        'headers' => $headers,
    ]);

    $body = $response->getBody();
    $decodedResponse = json_decode($body, true);
   // print_r($decodedResponse);
    if($decodedResponse['access_token']){
return $decodedResponse['access_token'];
    }
    else{
        return false;
    }

}


   function validateBVN($bvn)
{
    global $client, $dojaBase, $dojaSec, $dojaApp;
    // Digits only
    $bvn = preg_replace('/\D/', '', $bvn);

    if (strlen($bvn) !== 11) {
        return [
            'success' => false,
            'message' => 'Invalid BVN. Must be 11 digits.'
        ];
    }

    try {
        $q = $client->request('GET', $dojaBase . 'api/v1/kyc/bvn/full', [
            'query' => [
                'bvn' => $bvn,
            ],
            'headers' => [
                'Authorization' => $dojaSec,
                'AppId'         => $dojaApp,
                'Accept'        => 'application/json',
            ],
            'timeout' => 15, // safety timeout
        ]);

        $res = json_decode($q->getBody(), true);

        if (isset($res['entity']) && !empty($res['entity'])) {
            return [
                'success' => true,
                'data'    => $res['entity']
            ];
        }

        return [
            'success' => false,
            'message' => 'BVN validation failed. Make sure the BVN is correct.'
        ];

    } catch (Exception $e) {
         $error = parseError($e);
        return [
            'success' => false,
            'message' => parseError($error)
        ];
    }
}


function notifyAdmins(
    string $subject,
    string $body,
    array $fallbackEmails = ['feliwrites@gmail.com']
) {
    global $mysqli;

    $admins = [];

    $stmt = $mysqli->prepare("
        SELECT email
        FROM users
        WHERE is_admin = 1 AND is_banned = 0
    ");

    if (!$stmt) {
        error_log("Admin email query failed: " . $mysqli->error);
        return;
    }

    $stmt->execute();
    $res = $stmt->get_result();

    while ($row = $res->fetch_assoc()) {
        if (!empty($row['email']) && filter_var($row['email'], FILTER_VALIDATE_EMAIL)) {
            $admins[] = $row['email'];
        }
    }
    $stmt->close();

    // Fallback if no admin emails exist
    if (empty($admins)) {
        $admins = $fallbackEmails;
    }

    foreach ($admins as $adminEmail) {
        sendMail($adminEmail, $subject, $body);
    }
}

function parseError($e) {
    if ($e instanceof \GuzzleHttp\Exception\RequestException && $e->hasResponse()) {
        $body = $e->getResponse()->getBody()->getContents();
        $json = json_decode($body, true);

        if (isset($json['message'])) {
            return $json['message'];
        } elseif (isset($json['error'])) {
            return $json['error'];
        } elseif (is_string($body)) {
            return strip_tags(substr($body, 0, 200)); // fallback
        }
    }

    // If it's not a Guzzle error or no response
    return $e->getMessage() ?: 'An unexpected error occurred.';
}

function payRef(int $userId): void
{
    global $mysqli;
    $set = getSettings()[0];

    $rewardAmount  = $set['ref_bonus'] || 1000;
    $requiredSpend = $set['ref_spend'] || 10000;
    $appName = $set['app_name'];
    $time          = time();
if( $rewardAmount === 0){
    return;
}
    $mysqli->begin_transaction();

    try {
        // 🔒 Lock user
        $stmt = $mysqli->prepare("
            SELECT id, email, ref_by, ref_paid, total_spent 
            FROM users 
            WHERE id = ? 
            FOR UPDATE
        ");
        $stmt->bind_param("i", $userId);
        $stmt->execute();
        $user = $stmt->get_result()->fetch_assoc();
        $stmt->close();

        if (
            !$user ||
            empty($user['ref_by']) ||
            $user['ref_paid'] === 'yes' ||
            (float)$user['total_spent'] < $requiredSpend
        ) {
            $mysqli->commit();
            return; // ❗ exits function only
        }

        // 🔎 Find referrer
        $stmt = $mysqli->prepare("
            SELECT id, email, first_name, ngn_balance 
            FROM users 
            WHERE ref_code = ? 
            FOR UPDATE
        ");
        $stmt->bind_param("s", $user['ref_by']);
        $stmt->execute();
        $referrer = $stmt->get_result()->fetch_assoc();
        $stmt->close();

        if (!$referrer) {
            $mysqli->commit();
            return;
        }

        // 💰 Credit referrer
        $stmt = $mysqli->prepare("
            UPDATE users 
            SET ngn_balance = ngn_balance + ? 
            WHERE id = ?
        ");
        $stmt->bind_param("di", $rewardAmount, $referrer['id']);
        $stmt->execute();
        $stmt->close();

        // 🧾 Log transaction
        $ref = 'REF_' . bin2hex(random_bytes(6));

        $stmt = $mysqli->prepare("
            INSERT INTO transactions
            (flow, currency, type, title, description, date, status, our_ref, amount, email, user_id)
            VALUES
            ('in','NGN','referral_bonus','Referral Bonus',
             'Referral reward earned', ?, 'Completed', ?, ?, ?, ?)
        ");
        $stmt->bind_param(
            "isdsi",
            $time,
            $ref,
            $rewardAmount,
            $referrer['email'],
            $referrer['id']
        );
        $stmt->execute();
        $stmt->close();

        // ✅ Mark referral as paid
        $stmt = $mysqli->prepare("
            UPDATE users SET ref_paid = 'yes' WHERE id = ?
        ");
        $stmt->bind_param("i", $userId);
        $stmt->execute();
        $stmt->close();

        $mysqli->commit();

        // 🔔 In-app notification
        addNotification(
            $referrer['id'],
            " You earned ₦{$rewardAmount} referral bonus!",
            'You got paid!'
        );

        // 📧 Email referrer
        $subject = " Referral Bonus Earned!";
        $body = "
            <p>Hello {$referrer['first_name']},</p>

            <p>Great news! You’ve earned a <strong>₦{$rewardAmount}  referral bonus</strong>.</p>

            <p>Your referred user has successfully completed transactions worth ₦{$requiredSpend}  or more.</p>

            <ul>
                <li><strong>Bonus Amount:</strong> ₦{$rewardAmount} </li>
                <li><strong>Status:</strong> Credited</li>
                <li><strong>Date:</strong> " . date('Y-m-d H:i:s', $time) . "</li>
            </ul>

            <p>The bonus has been added to your wallet and is available immediately.</p>

            <p>Thank you for growing {$appName} 🚀</p>
        ";

        sendMail($referrer['email'], $subject, $body);

    } catch (Exception $e) {
        $mysqli->rollback();
        error_log("Referral reward error: " . $e->getMessage());
    }
}
 

function acquireFingerprintLock(string $fingerprint, string $action, int $seconds = 30): bool
{
    global $mysqli;

    $now = time();
    $expires = $now + $seconds;

    $stmt = $mysqli->prepare("
        INSERT INTO action_locks (fingerprint, action, locked_at)
        VALUES (?, ?, ?)
        ON DUPLICATE KEY UPDATE
            locked_at = IF(locked_at < ?, VALUES(locked_at), locked_at)
    ");

    $stmt->bind_param("ssii", $fingerprint, $action, $expires, $now);
    $stmt->execute();

    $ok = $stmt->affected_rows > 0;
    $stmt->close();

    return $ok;
}


function acquireActionLock(int $userId, string $action, int $ttl = 20): bool
{
    global $mysqli;
    $now = time();

    $stmt = $mysqli->prepare("
        INSERT INTO action_locks (user_id, action, locked_at)
        VALUES (?, ?, ?)
        ON DUPLICATE KEY UPDATE
            locked_at = IF(locked_at < ?, VALUES(locked_at), locked_at)
    ");

    $expiry = $now - $ttl;
    $stmt->bind_param("isii", $userId, $action, $now, $expiry);
    $stmt->execute();

    $success = $stmt->affected_rows === 1;
    $stmt->close();
    return $success;
}


function emailTemplate($subject, $message) {
        $set = getSettings()[0];

$appName = $set['app_name'];
    $year = date("Y");

    return "
    <html>
    <body style='margin:0;padding:0;background:#f4f4f4;font-family:Arial, sans-serif;'>

    <table width='100%' cellpadding='0' cellspacing='0' style='background:#f4f4f4;padding:20px 0;'>
      <tr>
        <td align='center'>

          <table width='600' cellpadding='0' cellspacing='0' style='background:#ffffff;border-radius:10px;overflow:hidden;box-shadow:0 4px 12px rgba(0,0,0,0.1);'>

            <!-- HEADER -->
            <tr>
              <td style='background:#001f3f;padding:20px;text-align:center;'>
                <img src='https://app.swapeki.com/assets/images/logo.png' width='150' alt='Swapeki' />
              </td>
            </tr>

            <!-- TITLE -->
            <tr>
              <td style='padding:25px;text-align:center;'>
                <h2 style='color:#001f3f;margin:0;font-size:24px;'>{$subject}</h2>
              </td>
            </tr>

            <!-- MESSAGE CONTENT -->
            <tr>
              <td style='padding:20px 30px;color:#333;font-size:16px;line-height:1.6;'>
                {$message}
              </td>
            </tr>

            <!-- DIVIDER -->
            <tr>
              <td style='padding:0 30px;'>
                <hr style='border:0;border-top:1px solid #ddd;' />
              </td>
            </tr>

            <!-- FOOTER -->
            <tr>
              <td style='padding:20px;text-align:center;color:#777;font-size:14px;'>
                You are receiving this email because you are a registered user of <b>{$appName} platform</b>.<br>
                © {$year} {$appName}. All Rights Reserved.
              </td>
            </tr>

          </table>

        </td>
      </tr>
    </table>

    </body>
    </html>
    ";
}


function getBearerToken() {
    // Get all headers in a case-insensitive way
    $headers = [];
    if (function_exists('getallheaders')) {
        $headers = getallheaders();
    } else {
        // Fallback for environments without getallheaders()
        foreach ($_SERVER as $name => $value) {
            if (substr($name, 0, 5) == 'HTTP_') {
                $key = str_replace(' ', '-', ucwords(strtolower(str_replace('_', ' ', substr($name, 5)))));
                $headers[$key] = $value;
            }
        }
    }

    // Find Authorization header
    $authHeader = $headers['Authorization'] ?? $headers['authorization'] ?? '';

    // Extract token from "Bearer <token>"
    if (preg_match('/Bearer\s(\S+)/', $authHeader, $matches)) {
        return trim($matches[1]);
    }

    return null; // No valid token found
}



function verifyAuthToken($token) {
    global $mysqli;
    $token = sanitizeInput($token);

    if (empty($token) || !$token) {
        return false;
    }

    $stmt = $mysqli->prepare("SELECT id, login_expiry FROM users WHERE login_token = ? LIMIT 1");
    if (!$stmt) {
        error_log("verifyAuthToken prepare failed: " . $mysqli->error);
        return false;
    }

    $stmt->bind_param("s", $token);
    $stmt->execute();
    $stmt->bind_result($userId, $expiry);
    $found = $stmt->fetch();
    $stmt->close();

    if (!$found) {
        return false;
    }

    // ✅ Check expiry
    if (time() > (int)$expiry) {
        $clear = $mysqli->prepare("UPDATE users SET login_token = NULL, login_expiry = NULL WHERE id = ?");
        $clear->bind_param("i", $userId);
        $clear->execute();
        $clear->close();
        return false;
    }

    return true;
}


function verifyUserBalance($userId, $cur = 'ngn', $max = 100.01) {
    global $mysqli;

    // ------------------------------
    // Normalize currency
    // ------------------------------
    $cur = strtolower(trim($cur));
    $allowed = ['ngn', 'usd', 'eur'];
    if (!in_array($cur, $allowed, true)) {
        $cur = 'ngn';
    }

    $currencyCode  = strtoupper($cur);
    $balanceColumn = $cur . '_balance';

    // ------------------------------
    // 1️⃣ Calculate expected balance
    // ------------------------------
    $stmt = $mysqli->prepare("
        SELECT 
            COALESCE(SUM(CASE 
                WHEN flow='in' AND status IN ('Complete','Completed') AND currency = ? 
                THEN amount ELSE 0 END), 0),
            COALESCE(SUM(CASE 
                WHEN flow='out' AND status IN ('Complete','Completed') AND currency = ? 
                THEN amount ELSE 0 END), 0)
        FROM transactions
        WHERE user_id = ?
    ");
    $stmt->bind_param("ssi", $currencyCode, $currencyCode, $userId);
    $stmt->execute();
    $stmt->bind_result($deposits, $withdrawals);
    $stmt->fetch();
    $stmt->close();

    $expected = round((float)$deposits - (float)$withdrawals, 2);

    // ------------------------------
    // 2️⃣ Fetch actual balance
    // ------------------------------
    $stmt = $mysqli->prepare("
        SELECT {$balanceColumn}, email, first_name 
        FROM users 
        WHERE id = ?
    ");
    $stmt->bind_param("i", $userId);
    $stmt->execute();
    $stmt->bind_result($actual, $email, $firstName);
    $stmt->fetch();
    $stmt->close();

    $actual     = round((float)$actual, 2);
    $difference = round($actual - $expected, 2);

    // ------------------------------
    // 3️⃣ Validate balance (FIXED LOGIC)
    // ------------------------------
    $valid = !($difference > $max);

    // ------------------------------
    // 4️⃣ If invalid → AUDIT + BAN + EMAIL
    // ------------------------------
    if (!$valid) {
        $ip   = $_SERVER['REMOTE_ADDR'] ?? 'CLI';
        $time = date('Y-m-d H:i:s');

        $details = "Expected {$currencyCode} " . number_format($expected, 2) .
                   ", Actual {$currencyCode} " . number_format($actual, 2);

        // 🔒 Start transaction
        $mysqli->begin_transaction();

        try {
            // ------------------------------
            // Insert audit log
            // ------------------------------
            $stmt = $mysqli->prepare("
                INSERT INTO audit_log
                (user_id, email, event_type, details, difference, currency, created_at, ip)
                VALUES (?, ?, 'balance_mismatch', ?, ?, ?, ?, ?)
            ");
            $stmt->bind_param(
                "issdsss",
                $userId,
                $email,
                $details,
                $difference,
                $currencyCode,
                $time,
                $ip
            );
            $stmt->execute();

            if ($stmt->errno) {
                throw new Exception("Audit insert failed: " . $stmt->error);
            }

            $stmt->close();

            // ------------------------------
            // Ban user + zero balance
            // ------------------------------
            $stmt = $mysqli->prepare("
                UPDATE users
                SET {$balanceColumn} = 0, is_banned = 1
                WHERE id = ?
            ");
            $stmt->bind_param("i", $userId);
            $stmt->execute();

            if ($stmt->errno) {
                throw new Exception("User ban failed: " . $stmt->error);
            }

            $stmt->close();

            // 🔒 Commit DB changes
            $mysqli->commit();

        } catch (Throwable $e) {
            $mysqli->rollback();
            error_log("SECURITY FAILURE [Balance Check]: " . $e->getMessage());
            return [
                'valid' => false,
                'error' => 'Security audit failed'
            ];
        }

        // ------------------------------
        // Notify admins (OUTSIDE TX)
        // ------------------------------
        $admins = [];
        $stmt = $mysqli->prepare("
            SELECT email
            FROM users
            WHERE is_admin = 1 AND is_banned = 0
        ");
        $stmt->execute();
        $res = $stmt->get_result();

        while ($row = $res->fetch_assoc()) {
            if (!empty($row['email'])) {
                $admins[] = $row['email'];
            }
        }
        $stmt->close();

        if (empty($admins)) {
            $admins[] = 'feliwrites@gmail.com';
        }

        $subject = "🚨 Balance Mismatch Detected: {$email}";
        $body = "
            <p><strong>Balance Discrepancy Detected</strong></p>
            <ul>
                <li>User: {$firstName} ({$email})</li>
                <li>User ID: {$userId}</li>
                <li>Currency: {$currencyCode}</li>
                <li>Expected: {$currencyCode} " . number_format($expected, 2) . "</li>
                <li>Actual: {$currencyCode} " . number_format($actual, 2) . "</li>
                <li>Difference: {$currencyCode} " . number_format($difference, 2) . "</li>
                <li>IP: {$ip}</li>
                <li>Time: {$time}</li>
            </ul>
            <p>User has been automatically restricted.</p>
        ";

        foreach ($admins as $adminEmail) {
            sendMail($adminEmail, $subject, $body);
        }
    }

    // ------------------------------
    // 5️⃣ Return result
    // ------------------------------
    return [
        'expected'   => $expected,
        'actual'     => $actual,
        'difference' => $difference,
        'valid'      => $valid,
        'email'      => $email,
        'currency'   => $currencyCode
    ];
}


function addNotification1($user_id, $text, $subject = null) {
    global $mysqli, $client, $set;
    $time = time();
    $projectId = "swapeki"; 
    $serviceAccountPath = __DIR__ . "/service-account.json";
$sbj = $subject ? $subject : $set['app_name'].' Notification';
     $u = getUserInfo($user_id);

    if (empty($u)) {
        return json_encode([
            'success' => false,
            'message' => 'User not found.'
        ]);
    }

    try {
        // ðŸ”¹ 1. Insert into database
        $stmt = $mysqli->prepare("
            INSERT INTO notifications (user_id, text, add_date) 
            VALUES (?, ?, ?)
        ");
        if (!$stmt) throw new Exception("Prepare failed: " . $mysqli->error);

        $stmt->bind_param("isi", $user_id, $text, $time);
        if (!$stmt->execute()) throw new Exception("Execute failed: " . $stmt->error);
        $stmt->close();

     $deviceToken = $u['fcm_token'];

        if (empty($deviceToken) || !$deviceToken) {
       
            return true; // DB insert still succeeded
        }

        // ðŸ”¹ 3. Build FCM payload
        $title = $sbj;
        $body  = $text;

        // ðŸ”¹ 4. Prepare Firebase credentials
        if (!file_exists($serviceAccountPath)) {
            throw new Exception("Service account not found at $serviceAccountPath");
        }

        $sa = json_decode(file_get_contents($serviceAccountPath), true);
        if (empty($sa['private_key']) || empty($sa['client_email'])) {
            throw new Exception("Invalid service account JSON structure.");
        }

        // ðŸ”¹ 5. Create short-lived access token
        $now = time();
        $jwtClaim = [
            "iss"   => $sa['client_email'],
            "scope" => "https://www.googleapis.com/auth/firebase.messaging",
            "aud"   => "https://oauth2.googleapis.com/token",
            "iat"   => $now,
            "exp"   => $now + 3600,
        ];
        $jwt = JWT::encode($jwtClaim, $sa['private_key'], 'RS256');

        $res = $client->post("https://oauth2.googleapis.com/token", [
            'form_params' => [
                'grant_type' => 'urn:ietf:params:oauth:grant-type:jwt-bearer',
                'assertion'  => $jwt,
            ],
        ]);
        $tokenResponse = json_decode($res->getBody(), true);
        if (empty($tokenResponse['access_token'])) {
            throw new Exception("Access token not returned by Google OAuth.");
        }
        $accessToken = $tokenResponse['access_token'];

        // ðŸ”¹ 6. Send push to FCM
        $fcmMessage = [
            "message" => [
                "token" => $deviceToken,
                "notification" => [
                    "title" => $title,
                    "body"  => $body,
                   // "image" => "",
                ],
                "android" => [
                    "priority" => "high",
                    "notification" => [
                        "icon" => "ic_notification",
                        "color" => "#373c72"
                    ],
                ],
                "apns" => [
                    "headers" => ["apns-priority" => "10"],
                ],
            ],
        ];

        $client->post("https://fcm.googleapis.com/v1/projects/{$projectId}/messages:send", [
            'headers' => [
                'Authorization' => "Bearer {$accessToken}",
                'Content-Type'  => 'application/json',
            ],
            'json' => $fcmMessage,
        ]);

        return true;

    } catch (RequestException $e) {
        $response = $e->hasResponse() ? $e->getResponse()->getBody()->getContents() : $e->getMessage();
        error_log("FCM Request Error: " . $response);
        return false;
    } catch (Exception $e) {
        error_log("Notification Error: " . $e->getMessage());
        return false;
    }
}

function addNotification($user_id, $text, $subject = null) {
    global $mysqli, $client, $set;

    $time = time();
    $projectId = "swapeki";
    $serviceAccountPath = __DIR__ . "/service-account.json";

    $sbj = $subject ?: ($set['app_name'] . ' Notification');

   // error_log("[FCM] ===== Notification START =====");
   // error_log("[FCM] User ID: {$user_id}");
   // error_log("[FCM] Message: {$text}");

    $u = getUserInfo($user_id);

    if (empty($u)) {
      //  error_log("[FCM][ERROR] User not found");
        return false;
    }

   // error_log("[FCM] User found: {$u['email']}");

    try {
        /* ------------------ 1️⃣ DB INSERT ------------------ */
        $stmt = $mysqli->prepare("
            INSERT INTO notifications (user_id, text, add_date)
            VALUES (?, ?, ?)
        ");
        if (!$stmt) {
            throw new Exception("Prepare failed: " . $mysqli->error);
        }

        $stmt->bind_param("isi", $user_id, $text, $time);
        if (!$stmt->execute()) {
            throw new Exception("Execute failed: " . $stmt->error);
        }
        $stmt->close();

       // error_log("[FCM] Notification inserted into DB");

        /* ------------------ 2️⃣ TOKEN CHECK ------------------ */
        $deviceToken = $u['fcm_token'] ?? null;

        if (!$deviceToken) {
         //   error_log("[FCM][WARN] No device token found for user");
            return true; // DB insert still OK
        }

      //  error_log("[FCM] Device token: {$deviceToken}");

        /* ------------------ 3️⃣ SERVICE ACCOUNT ------------------ */
        if (!file_exists($serviceAccountPath)) {
            throw new Exception("Service account not found at {$serviceAccountPath}");
        }

        $sa = json_decode(file_get_contents($serviceAccountPath), true);
        if (empty($sa['private_key']) || empty($sa['client_email'])) {
            throw new Exception("Invalid service account JSON");
        }

       // error_log("[FCM] Service account loaded: {$sa['client_email']}");

        /* ------------------ 4️⃣ OAUTH TOKEN ------------------ */
        $now = time();
        $jwtClaim = [
            "iss"   => $sa['client_email'],
            "scope" => "https://www.googleapis.com/auth/firebase.messaging",
            "aud"   => "https://oauth2.googleapis.com/token",
            "iat"   => $now,
            "exp"   => $now + 3600,
        ];

        $jwt = JWT::encode($jwtClaim, $sa['private_key'], 'RS256');

        $res = $client->post("https://oauth2.googleapis.com/token", [
            'form_params' => [
                'grant_type' => 'urn:ietf:params:oauth:grant-type:jwt-bearer',
                'assertion'  => $jwt,
            ],
        ]);

        $tokenResponse = json_decode($res->getBody(), true);
        if (empty($tokenResponse['access_token'])) {
            throw new Exception("OAuth token missing");
        }

        $accessToken = $tokenResponse['access_token'];
       // error_log("[FCM] OAuth token acquired");

        /* ------------------ 5️⃣ FCM PAYLOAD (FIXED) ------------------ */
        $fcmMessage = [
            "message" => [
                "token" => $deviceToken,

                // 🔑 ALWAYS DELIVERED (foreground + background)
                "data" => [
                    "type"  => "general",
                    "title" => $sbj,
                    "body"  => $text,
                ],

                // 🔔 SYSTEM NOTIFICATION
                "notification" => [
                    "title" => $sbj,
                    "body"  => $text,
                ],

                "android" => [
                    "priority" => "HIGH",
                    "notification" => [
                        "channel_id" => "default",
                        "icon"  => "ic_notification",
                        "color" => "#3771c7ff",
                    ],
                ],

                "apns" => [
                    "headers" => [
                        "apns-priority" => "10"
                    ],
                    "payload" => [
                        "aps" => [
                            "sound" => "default"
                        ]
                    ]
                ],
            ],
        ];

       // error_log("[FCM] Sending payload: " . json_encode($fcmMessage));

        /* ------------------ 6️⃣ SEND ------------------ */
        $resp = $client->post(
            "https://fcm.googleapis.com/v1/projects/{$projectId}/messages:send",
            [
                'headers' => [
                    'Authorization' => "Bearer {$accessToken}",
                    'Content-Type'  => 'application/json',
                ],
                'json' => $fcmMessage,
            ]
        );

        $respBody = $resp->getBody()->getContents();
      //  error_log("[FCM][SUCCESS] Firebase response: {$respBody}");
       // error_log("[FCM] ===== Notification END =====");

        return true;

    } catch (RequestException $e) {
        $response = $e->hasResponse()
            ? $e->getResponse()->getBody()->getContents()
            : $e->getMessage();

        error_log("[FCM][REQUEST ERROR] {$response}");
        return false;

    } catch (Exception $e) {
        error_log("[FCM][ERROR] " . $e->getMessage());
        return false;
    }
}


//addNotification(19, 'Testing testing', 'It worked'); 

function generateReceipt($transactionId, $bodyHtml) {
    $logoPath = __DIR__ . "/logo.png";
    $set = getSettings()[0];
    $app = strtoupper($set['app_name']);

    // Create TCPDF instance
    $pdf = new TCPDF(PDF_PAGE_ORIENTATION, PDF_UNIT, PDF_PAGE_FORMAT, true, 'UTF-8', false);

    // Set document info
    $pdf->SetCreator($set['app_name']);
    $pdf->SetAuthor($app);
    $pdf->SetTitle('Transaction Receipt');
    $pdf->SetSubject('Transaction Receipt');

    // Disable default header/footer
    $pdf->setPrintHeader(false);
    $pdf->setPrintFooter(false);

    // Add a page
    $pdf->AddPage();

    // Create HTML header with logo
    $logoHtml = '';
    if (file_exists($logoPath)) {
        $logoHtml = '<img src="' . $logoPath . '" width="120" style="margin-bottom:5px;"><br>';
    }

    $header = '
        <div style="text-align:center; padding:10px; border-bottom:2px solid #f1f1f1;">
            ' . $logoHtml . '
           
            <p style="color:#888; font-size:13px;">Secure. Fast. Reliable.</p>
        </div>
    ';

    $footer = '
        <div style="text-align:center; padding:8px; font-size:12px; border-top:2px solid #1584d4; margin-top:30px; color:#555;">
            &copy; ' . date("Y") . ' ' . $app . ' â€¢ All Rights Reserved<br>
            Need help? Email us at <a href="mailto:info@swapeki.com">info@swapeki.com</a>
        </div>
    ';

    // Combine full HTML
    $fullHtml = '
    <style>
        body {
            font-family: "Helvetica Neue", Arial, sans-serif;
            font-size: 14px;
            color: #333;
        }
        .receipt-container {
            margin: 20px auto;
            padding: 20px;
            background: #fff;
            border: 1px solid #eee;
            border-radius: 10px;
        }
        table {
            width: 100%;
            border-collapse: collapse;
            margin-top: 15px;
        }
        td, th {
            padding: 8px;
            text-align: left;
            border-bottom: 1px solid #ddd;
        }
        h3, .success {
            color: green;
        }
    </style>
    ' . $header . '
    <div class="receipt-container">' . $bodyHtml . '</div>
    ' . $footer;

    // Output the HTML into PDF
    $pdf->writeHTML($fullHtml, true, false, true, false, '');

    // Save PDF to disk
    $directory = __DIR__ . "/uploads/receipts/";
    if (!is_dir($directory)) {
        mkdir($directory, 0775, true);
    }

    $filePath = $directory . "receipt-" . $transactionId . ".pdf";
    $pdf->Output($filePath, 'F');

    return "uploads/receipts/receipt-" . $transactionId . ".pdf";
}



function getArray($table, $where = '', $orderBy = '', $limit = '') {
    global $mysqli;
    
    try {
        // Start building the SQL query
        $sql = "SELECT * FROM `$table`";
        
        // Append WHERE clause if provided
        if (!empty($where)) {
            $sql .= " WHERE $where";
        }
        
        // Append ORDER BY clause if provided
        if (!empty($orderBy)) {
            $sql .= " ORDER BY $orderBy";
        }
        
        // Append LIMIT clause if provided
        if (!empty($limit)) {
            $sql .= " LIMIT $limit";
        }
        
        // Prepare the statement
        $stmt = $mysqli->prepare($sql);
        if (!$stmt) {
            return ['error' => $mysqli->error];
        }
        
        // Execute the query
        $stmt->execute();
        $result = $stmt->get_result();
        
        // Fetch all results as an associative array
        $data = [];
        while ($row = $result->fetch_assoc()) {
            $data[] = $row;
        }
        
        // Close statement
        $stmt->close();
        
        return $data ?: []; // Return empty array if no records found
    } catch (Exception $e) {
        // Handle errors
        return ['error' => $e->getMessage()];
    }
}



function sendMail($to, $subject, $text) {
    $set = getSettings()[0];
    $mail = new PHPMailer(true);

    try {
        // Server settings
        $mail->isSMTP();
        $mail->Host       = $set['smtp_host'];
        $mail->SMTPAuth   = true;
        $mail->Username   = $set['smtp_email'];
        $mail->Password   = $set['smtp_password'];
        $mail->SMTPSecure = PHPMailer::ENCRYPTION_STARTTLS;   // Enable TLS encryption
        $mail->Port = 587;

        // Recipients
        $mail->setFrom($set['smtp_email'], $set['app_name']);
        $mail->addAddress($to);

        // Content
        $mail->isHTML(true);                                  // Set email format to HTML
        $mail->Subject = $subject;
        $mail->Body    = "<html><head><title>$subject</title></head><body><p>$text</p></body></html>";
        $mail->AltBody = strip_tags($text);                   // Plain text for non-HTML mail clients

        // Send the email
        $mail->send();
        return true;
    } catch (Exception $e) {
        // Log or handle error as needed
        error_log("Mailer Error: " . $mail->ErrorInfo);
        return false;
    }
}

function getUserInfo($id) {
    global $mysqli;
$id = sanitizeInput($id);
    // Initialize user info as an empty array
    $user = [];

    // Prepare the SQL statement
    $stmt1 = $mysqli->prepare("SELECT * FROM users WHERE email = ? OR username = ? OR quidax_email = ? OR id = ? OR login_token = ? OR ref_code = ?");
    if (!$stmt1) {
        // Log and return empty result
        error_log("Failed to prepare statement: " . $mysqli->error);
        return null;
    }

    // Bind parameters and execute
    $stmt1->bind_param("ssssss", $id, $id, $id, $id, $id, $id);
    if (!$stmt1->execute()) {
        // Log and return empty result
        error_log("Failed to execute statement: " . $stmt1->error);
        $stmt1->close();
        return null;
    }

    // Fetch result
    $result1 = $stmt1->get_result();
    if ($result1 && $result1->num_rows > 0) {
        $user = $result1->fetch_assoc();
    }

    // Free the result set and close the statement
    $result1->free();
    $stmt1->close();

    return $user;
}

function sanitizeInput($input, $type = 'string') {
    if (is_array($input)) {
        return array_map(function ($item) use ($type) {
            return sanitizeInput($item, $type);
        }, $input);
    }

    // Trim white space
    $input = trim($input);

    // Remove invisible characters
    $input = preg_replace('/[\x00-\x1F\x7F]/u', '', $input);

    // Reject null bytes
    if (strpos($input, "\0") !== false) {
        return null;
    }

    switch ($type) {
        case 'int':
            if (filter_var($input, FILTER_VALIDATE_INT) !== false) {
                return (int)$input;
            }
            break;

        case 'float':
            if (filter_var($input, FILTER_VALIDATE_FLOAT) !== false) {
                return (float)$input;
            }
            break;

        case 'email':
            $sanitized = filter_var($input, FILTER_SANITIZE_EMAIL);
            if (filter_var($sanitized, FILTER_VALIDATE_EMAIL)) {
                return $sanitized;
            }
            break;

        case 'url':
            $sanitized = filter_var($input, FILTER_SANITIZE_URL);
            if (filter_var($sanitized, FILTER_VALIDATE_URL)) {
                return $sanitized;
            }
            break;

        case 'boolean':
            return filter_var($input, FILTER_VALIDATE_BOOLEAN, FILTER_NULL_ON_FAILURE);

        case 'html':
            // Remove JS events and script tags
            $input = strip_tags($input, '<p><br><b><i><u><strong><em><a><ul><ol><li><h1><h2><h3><h4><h5><h6>');
            $input = preg_replace('/on\w+="[^"]+"/i', '', $input);
            $input = preg_replace('/<script\b[^>]*>(.*?)<\/script>/is', '', $input);
            return htmlspecialchars($input, ENT_QUOTES | ENT_HTML5, 'UTF-8');

        case 'alphanum':
            if (preg_match('/^[a-zA-Z0-9_]+$/', $input)) {
                return $input;
            }
            break;

        case 'string':
        default:
            // Remove HTML tags and encode special chars
            $input = strip_tags($input);
            $input = htmlspecialchars($input, ENT_QUOTES | ENT_HTML5, 'UTF-8');
            // Prevent SQL wildcard abuse
            $input = str_replace(['%', '_'], ['\%', '\_'], $input);
            return $input;
    }

    // If validation failed
    return null;
}



function checkExistVal($table, $col, $val){
    global $mysqli;
    $userCheckQuery = "SELECT COUNT(*) as count FROM $table WHERE $col = ?";
$userCheckStmt = $mysqli->prepare($userCheckQuery);
$userCheckStmt->bind_param("s", $val);
$userCheckStmt->execute();
$userCheckResult = $userCheckStmt->get_result();
$userExists = $userCheckResult->fetch_assoc()['count'];
if ($userExists == 0) {
    return false;
}
else{
    return true;
}



}

function uploadFile($file) {
    global $uploadDir, $baseURL;
 //$targetFile = $uploadDir . basename($file["name"]);
 $uniqueName = uniqid() . '_' . basename($file["name"]);
 $targetFile = $uploadDir . $uniqueName;
 $uploadOk = 1;
 $error = "";
 $fileType = strtolower(pathinfo($targetFile, PATHINFO_EXTENSION));

 // Check if file already exists
 //if (file_exists($targetFile)) {
  //   $uploadOk = 0;
  //   $error = "File already exists.";
 //}

 // Check file size (limit to 500MB)
 if ($file["size"] > 500000000) {
     $uploadOk = 0;
     $error = "File is too large.";
 }

 // Allow certain file formats
 if ($fileType != "jpg" && $fileType != "png" && $fileType != "jpeg" && $fileType != "mp4") {
     $uploadOk = 0;
     $error = "Invalid file format. Upload only jpg, jpeg, png and  gif for images and mp4 for videos.";
 }

 // Check if $uploadOk is set to 0 by an error
 if ($uploadOk == 0) {
     return ["filePath" => false, "error" => $error];
 } else {
     if (move_uploaded_file($file["tmp_name"], $targetFile)) {
         return ["filePath" => $baseURL . $uniqueName, "error" => ""];
     } else {
         return ["filePath" => false, "error" => "Failed to move uploaded file."];
     }
 }
}

function getSettings(){
    global $mysqli;
    $Query = "SELECT * FROM settings";
    $result = $mysqli->query($Query);
    
    if ($result->num_rows > 0) {
        $records = [];
        while ($row = $result->fetch_assoc()) {
            $records[] = $row;
        }
        return $records;
    } else {
        return null;
    }
}

function getPercentage($number, $percent) {
    return ($number * $percent) / 100;
  }

  function isValidEmail($email) {
    return filter_var($email, FILTER_VALIDATE_EMAIL) !== false;
}



?>