<?php
/**
 * سودانا — SudannaPayEngine v1
 * القلب المالي الكامل لـ Sudanna Pay
 * يتعامل مع: حسابات، تحويلات، QR، وكلاء، فواتير
 */
require_once __DIR__ . '/AccountingEngine.php';
require_once __DIR__ . '/BankAdapter.php';

class SudannaPayEngine {

    const SUDANNA_MAIN_ACCOUNT = '1821425';
    const MIN_TRANSFER   = 10;
    const MAX_TRANSFER   = 10000000;
    const REVERSAL_MINS  = 15;
    const IDEMPOTENCY_TTL = 3600; // 1 ساعة

    // ── createAccount(): إنشاء حساب Pay لمستخدم ──────────────
    public static function createAccount(int $userId): array {
        try {
            // تحقق أن الحساب غير موجود
            $existing = db()->prepare("SELECT id FROM sdpay_accounts WHERE user_id=? LIMIT 1");
            $existing->execute([$userId]);
            if ($existing->fetch()) return ['ok'=>false,'error'=>'account_exists'];

            $accountNo = self::generateAccountNo();
            db()->prepare(
                "INSERT INTO sdpay_accounts (user_id,account_no,pin_hash,balance_sdg,status)
                 VALUES (?,?,'',0,'active')"
            )->execute([$userId, $accountNo]);

            return ['ok'=>true, 'account_no'=>$accountNo];
        } catch (Throwable $e) {
            error_log("[SudannaPayEngine::createAccount] " . $e->getMessage());
            return ['ok'=>false, 'error'=>$e->getMessage()];
        }
    }

    // ── getAccount(): جلب الحساب ──────────────────────────────
    public static function getAccount(int $userId): ?array {
        try {
            $st = db()->prepare("SELECT * FROM sdpay_accounts WHERE user_id=? LIMIT 1");
            $st->execute([$userId]);
            return $st->fetch() ?: null;
        } catch (Throwable $e) { return null; }
    }

    // ── setPin(): تعيين/تغيير PIN ─────────────────────────────
    public static function setPin(int $userId, string $pin): array {
        if (!preg_match('/^\d{4,6}$/', $pin))
            return ['ok'=>false,'error'=>'invalid_pin_format'];
        try {
            db()->prepare("UPDATE sdpay_accounts SET pin_hash=? WHERE user_id=?")
               ->execute([password_hash($pin, PASSWORD_BCRYPT), $userId]);
            return ['ok'=>true];
        } catch (Throwable $e) { return ['ok'=>false,'error'=>$e->getMessage()]; }
    }

    // ── verifyPin(): التحقق من PIN ────────────────────────────
    public static function verifyPin(int $userId, string $pin): bool {
        try {
            $st = db()->prepare("SELECT pin_hash, failed_pin_attempts, pin_locked_until FROM sdpay_accounts WHERE user_id=? LIMIT 1");
            $st->execute([$userId]);
            $row = $st->fetch();
            if (!$row || empty($row['pin_hash'])) return false;

            // ── PIN Lockout ──────────────────────────────────────────
            if (!empty($row['pin_locked_until']) && strtotime($row['pin_locked_until']) > time()) {
                $minutesLeft = ceil((strtotime($row['pin_locked_until']) - time()) / 60);
                return false; // مقفل — انتظر $minutesLeft دقيقة
            }

            $isValid = password_verify($pin, $row['pin_hash']);
            $maxAttempts = defined('PIN_MAX_ATTEMPTS')    ? (int)PIN_MAX_ATTEMPTS    : 3;
            $lockMins    = defined('PIN_LOCKOUT_MINUTES') ? (int)PIN_LOCKOUT_MINUTES : 15;

            if ($isValid) {
                // إعادة العداد
                db()->prepare("UPDATE sdpay_accounts SET failed_pin_attempts=0, pin_locked_until=NULL WHERE user_id=?")->execute([$userId]);
            } else {
                $newAttempts = (int)($row['failed_pin_attempts'] ?? 0) + 1;
                $lockUntil   = $newAttempts >= $maxAttempts
                    ? date('Y-m-d H:i:s', time() + $lockMins * 60)
                    : null;
                db()->prepare(
                    "UPDATE sdpay_accounts SET failed_pin_attempts=?, pin_locked_until=? WHERE user_id=?"
                )->execute([$newAttempts, $lockUntil, $userId]);
            }
            return $isValid;
        } catch (Throwable $e) {
            error_log("[SudannaPayEngine::verifyPin] " . $e->getMessage());
            return false;
        }
    }

    // ── transfer(): تحويل فوري ────────────────────────────────
    public static function transfer(
        int    $fromUserId,
        string $toAccountNo,
        float  $amount,
        string $narration = '',
        string $idempotencyKey = '',
        string $pin = ''
    ): array {
        // 1. تحقق من Idempotency
        if ($idempotencyKey) {
            $dup = self::checkIdempotency($idempotencyKey);
            if ($dup) return $dup;
        }

        // 2. تحقق من PIN
        if ($pin && !self::verifyPin($fromUserId, $pin))
            return ['ok'=>false,'error'=>'invalid_pin','message'=>'الرقم السري غير صحيح'];

        // 3. التحقق من المبالغ
        if ($amount < self::MIN_TRANSFER)
            return ['ok'=>false,'error'=>'below_minimum','message'=>'الحد الأدنى للتحويل 10 SDG'];

        try {
            // 4. بدء Transaction

            // ══ BANK ADAPTER: إرسال للمؤسسة المالية أولاً ═════════════
            if (defined('TRANSFER_REQUIRES_BANK_CONFIRM') && TRANSFER_REQUIRES_BANK_CONFIRM) {
                $fromBankAcc = $fromAcc['bank_account_no'] ?? null;
                $toBankAcc   = $toAcc['bank_account_no']   ?? null;
                $instId      = (int)($fromAcc['bank_institution_id'] ?? 0);
                $bankResult  = BankAdapter::initiateTransfer(
                    $fromBankAcc ?: self::SUDANNA_MAIN_ACCOUNT,
                    $toBankAcc   ?: self::SUDANNA_MAIN_ACCOUNT,
                    $amount, $txnRef, $narration, $instId
                );
                if (!($bankResult['confirmed'] ?? false) && !($bankResult['sandbox'] ?? false)) {
                    return [
                        'ok'         => false,
                        'error'      => 'bank_rejected',
                        'error_code' => $bankResult['error_code'] ?? 'BANK_ERR',
                        'message'    => 'رُفض التحويل من المؤسسة المالية: ' . ($bankResult['error_msg'] ?? 'خطأ غير محدد'),
                    ];
                }
                $bankTxnId = $bankResult['bank_txn_id'] ?? null;
            } else {
                $bankTxnId = null;
            }

            db()->beginTransaction();

            // 5. قفل حساب المُرسِل
            $from = db()->prepare(
                "SELECT a.*, u.name FROM sdpay_accounts a
                 JOIN users u ON u.id=a.user_id
                 WHERE a.user_id=? AND a.status='active' FOR UPDATE"
            );
            $from->execute([$fromUserId]);
            $fromAcc = $from->fetch();

            if (!$fromAcc) {
                db()->rollBack();
                return ['ok'=>false,'error'=>'from_account_not_found'];
            }

            // 6. تحقق من الرصيد
            $available = $fromAcc['balance_sdg'] - $fromAcc['hold_sdg'];
            if ($available < $amount) {
                db()->rollBack();
                return ['ok'=>false,'error'=>'insufficient_balance',
                    'available'=>round($available,2),'required'=>$amount];
            }

            // 7. قفل حساب المستلم
            $to = db()->prepare(
                "SELECT a.*, u.name FROM sdpay_accounts a
                 JOIN users u ON u.id=a.user_id
                 WHERE a.account_no=? AND a.status='active' FOR UPDATE"
            );
            $to->execute([$toAccountNo]);
            $toAcc = $to->fetch();

            if (!$toAcc) {
                db()->rollBack();
                return ['ok'=>false,'error'=>'recipient_not_found',
                    'message'=>'لم يُعثَر على الحساب المستهدف'];
            }

            // 8. حساب الرسوم
            $fee = self::calculateFee($amount, 'transfer');
            $net = $amount - $fee;

            // 9. تنفيذ التحويل
            $txnRef = self::generateTxnRef();
            $canReverse = date('Y-m-d H:i:s', time() + self::REVERSAL_MINS * 60);

            // خصم من المُرسِل
            db()->prepare(
                "UPDATE sdpay_accounts SET balance_sdg=balance_sdg-?,last_txn_at=NOW() WHERE user_id=?"
            )->execute([$amount, $fromUserId]);

            // إضافة للمستلم
            db()->prepare(
                "UPDATE sdpay_accounts SET balance_sdg=balance_sdg+?,last_txn_at=NOW() WHERE account_no=?"
            )->execute([$net, $toAccountNo]);

            // تسجيل المعاملة
            db()->prepare(
                "INSERT INTO sdpay_transactions
                 (txn_ref,idempotency_key,type,from_account,to_account,
                  amount_sdg,fee_sdg,net_sdg,status,narration,can_reverse_until,completed_at)
                 VALUES (?,?,'transfer',?,?,?,?,?,'completed',?,?,NOW())"
            )->execute([
                $txnRef, $idempotencyKey ?: null,
                $fromAcc['account_no'], $toAccountNo,
                $amount, $fee, $net, $narration ?: 'تحويل', $canReverse
            ]);

            // حفظ Idempotency
            if ($idempotencyKey)
                self::saveIdempotency($idempotencyKey, ['ok'=>true,'txn_ref'=>$txnRef]);

            db()->commit();

            // 10. إشعارات
            try {
                self::notifyUser($fromUserId, 'payment_sent',
                    '💸 تحويل صادر',
                    "حوَّلت " . number_format($amount) . " SDG لـ " . ($toAcc['name'] ?? $toAccountNo));
                self::notifyUser($toAcc['user_id'], 'payment_received',
                    '💰 تحويل وارد',
                    "استلمت " . number_format($net) . " SDG من " . ($fromAcc['name'] ?? 'مستخدم'));
            } catch (Throwable $ignored) {}

            // 11. AML check
            if ($amount >= 10000000) self::flagAML($fromUserId, $txnRef, $amount);

            return [
                'ok'        => true,
                'txn_ref'   => $txnRef,
                'amount'    => $amount,
                'fee'       => $fee,
                'net'       => $net,
                'to_name'   => $toAcc['name'] ?? $toAccountNo,
                'can_reverse_until' => $canReverse,
            ];

        } catch (Throwable $e) {
            try { db()->rollBack(); } catch (Throwable $ignored) {}
            error_log("[SudannaPayEngine::transfer] " . $e->getMessage());
            return ['ok'=>false,'error'=>'system_error','message'=>$e->getMessage()];
        }
    }

    // ── payMerchant(): دفع للتاجر ─────────────────────────────
    public static function payMerchant(
        int    $fromUserId,
        string $merchantCode,
        float  $amount,
        string $pin,
        string $narration = ''
    ): array {
        if (!self::verifyPin($fromUserId, $pin))
            return ['ok'=>false,'error'=>'invalid_pin'];
        try {
            $merch = db()->prepare(
                "SELECT m.*, a.account_no FROM sdpay_merchants m
                 JOIN sdpay_accounts a ON a.user_id=m.user_id
                 WHERE m.merchant_code=? AND m.status='active' LIMIT 1"
            );
            $merch->execute([$merchantCode]);
            $merchant = $merch->fetch();
            if (!$merchant) return ['ok'=>false,'error'=>'merchant_not_found'];

            $result = self::transfer(
                $fromUserId, $merchant['account_no'], $amount,
                $narration ?: 'دفع لـ ' . $merchant['business_name'], '', ''
            );

            if ($result['ok']) {
                // تحديث إحصاءات التاجر
                db()->prepare(
                    "UPDATE sdpay_merchants SET monthly_volume_sdg=monthly_volume_sdg+? WHERE merchant_code=?"
                )->execute([$amount, $merchantCode]);

                // نقاط ولاء
                self::awardLoyaltyPoints($fromUserId, (int)$merchant['id'], $amount);
            }
            return $result;
        } catch (Throwable $e) {
            return ['ok'=>false,'error'=>$e->getMessage()];
        }
    }

    // ── reverseTransaction(): إلغاء معاملة ───────────────────
    public static function reverseTransaction(int $userId, string $txnRef): array {
        try {
            $txn = db()->prepare(
                "SELECT * FROM sdpay_transactions WHERE txn_ref=? AND status='completed' LIMIT 1"
            );
            $txn->execute([$txnRef]);
            $t = $txn->fetch();

            if (!$t) return ['ok'=>false,'error'=>'transaction_not_found'];

            // تحقق من الوقت
            if ($t['can_reverse_until'] && strtotime($t['can_reverse_until']) < time())
                return ['ok'=>false,'error'=>'reversal_window_expired',
                    'message'=>'انتهت مدة الإلغاء (15 دقيقة)'];

            // تحقق من الملكية
            $fromAcc = db()->prepare("SELECT account_no FROM sdpay_accounts WHERE user_id=? LIMIT 1");
            $fromAcc->execute([$userId]);
            $acc = $fromAcc->fetch();
            if (!$acc || $acc['account_no'] !== $t['from_account'])
                return ['ok'=>false,'error'=>'unauthorized'];

            // تنفيذ الإلغاء
            db()->beginTransaction();
            db()->prepare("UPDATE sdpay_accounts SET balance_sdg=balance_sdg+? WHERE account_no=?")
               ->execute([$t['amount_sdg'], $t['from_account']]);
            db()->prepare("UPDATE sdpay_accounts SET balance_sdg=balance_sdg-? WHERE account_no=?")
               ->execute([$t['net_sdg'], $t['to_account']]);
            db()->prepare(
                "UPDATE sdpay_transactions SET status='reversed' WHERE txn_ref=?"
            )->execute([$txnRef]);

            $revRef = self::generateTxnRef('REV');
            db()->prepare(
                "INSERT INTO sdpay_transactions
                 (txn_ref,type,from_account,to_account,amount_sdg,fee_sdg,net_sdg,
                  status,narration,reversal_of,completed_at)
                 VALUES (?,'refund',?,?,?,0,?,'completed',?,?,NOW())"
            )->execute([
                $revRef, $t['to_account'], $t['from_account'],
                $t['amount_sdg'], $t['amount_sdg'],
                'إلغاء معاملة ' . $txnRef, $t['id']
            ]);
            db()->commit();

            return ['ok'=>true,'reversal_ref'=>$revRef];
        } catch (Throwable $e) {
            try { db()->rollBack(); } catch (Throwable $i) {}
            return ['ok'=>false,'error'=>$e->getMessage()];
        }
    }

    // ── cashin(): شحن من وكيل ────────────────────────────────
    public static function cashin(int $agentUserId, string $userAccountNo, float $amount): array {
        try {
            $agent = db()->prepare(
                "SELECT * FROM sdpay_agents WHERE user_id=? AND status='active' LIMIT 1"
            );
            $agent->execute([$agentUserId]);
            $agentRow = $agent->fetch();
            if (!$agentRow) return ['ok'=>false,'error'=>'agent_not_found'];

            db()->beginTransaction();

            // إضافة للمستخدم
            db()->prepare(
                "UPDATE sdpay_accounts SET balance_sdg=balance_sdg+?,last_txn_at=NOW()
                 WHERE account_no=? AND status='active'"
            )->execute([$amount, $userAccountNo]);

            if (!db()->rowCount()) {
                db()->rollBack();
                return ['ok'=>false,'error'=>'user_account_not_found'];
            }

            // عمولة الوكيل
            $commission = round($amount * $agentRow['commission_rate'], 2);
            db()->prepare(
                "UPDATE sdpay_agents SET txn_count=txn_count+1,
                 total_earned_sdg=total_earned_sdg+? WHERE user_id=?"
            )->execute([$commission, $agentUserId]);

            // تسجيل
            $txnRef = self::generateTxnRef('CI');
            db()->prepare(
                "INSERT INTO sdpay_transactions
                 (txn_ref,type,to_account,amount_sdg,fee_sdg,net_sdg,status,channel,agent_id,completed_at)
                 VALUES (?,'cashin',?,?,0,?,'completed','agent',?,NOW())"
            )->execute([$txnRef, $userAccountNo, $amount, $amount, $agentRow['id']]);

            db()->prepare(
                "INSERT INTO sdpay_agent_transactions
                 (agent_id,txn_ref,type,user_account,amount_sdg,commission_sdg,status)
                 VALUES (?,?,'cashin',?,?,?,'completed')"
            )->execute([$agentRow['id'], $txnRef, $userAccountNo, $amount, $commission]);

            db()->commit();

            // إشعار المستخدم
            try {
                $userAcc = db()->prepare("SELECT user_id FROM sdpay_accounts WHERE account_no=? LIMIT 1");
                $userAcc->execute([$userAccountNo]);
                $uRow = $userAcc->fetch();
                if ($uRow) self::notifyUser((int)$uRow['user_id'], 'cashin',
                    '✅ تم شحن المحفظة',
                    "شُحِنَت محفظتك بـ " . number_format($amount) . " SDG عبر الوكيل");
            } catch (Throwable $ignored) {}

            return ['ok'=>true,'txn_ref'=>$txnRef,'amount'=>$amount,'commission'=>$commission];
        } catch (Throwable $e) {
            try { db()->rollBack(); } catch (Throwable $i) {}
            return ['ok'=>false,'error'=>$e->getMessage()];
        }
    }

    // ── cashout(): سحب عبر وكيل ──────────────────────────────
    public static function cashout(int $userId, int $agentUserId, float $amount, string $pin): array {
        if (!self::verifyPin($userId, $pin))
            return ['ok'=>false,'error'=>'invalid_pin'];
        try {
            $agent = db()->prepare("SELECT * FROM sdpay_agents WHERE user_id=? AND status='active' LIMIT 1");
            $agent->execute([$agentUserId]);
            $agentRow = $agent->fetch();
            if (!$agentRow) return ['ok'=>false,'error'=>'agent_not_found'];

            $acc = self::getAccount($userId);
            if (!$acc || ($acc['balance_sdg'] - $acc['hold_sdg']) < $amount)
                return ['ok'=>false,'error'=>'insufficient_balance'];

            db()->beginTransaction();
            $commission = round($amount * $agentRow['commission_rate'], 2);
            $fee = $commission;

            db()->prepare(
                "UPDATE sdpay_accounts SET balance_sdg=balance_sdg-?,last_txn_at=NOW() WHERE user_id=?"
            )->execute([$amount, $userId]);

            db()->prepare(
                "UPDATE sdpay_agents SET txn_count=txn_count+1,total_earned_sdg=total_earned_sdg+? WHERE user_id=?"
            )->execute([$commission, $agentUserId]);

            $txnRef = self::generateTxnRef('CO');
            db()->prepare(
                "INSERT INTO sdpay_transactions
                 (txn_ref,type,from_account,amount_sdg,fee_sdg,net_sdg,status,channel,agent_id,completed_at)
                 VALUES (?,'cashout',?,?,?,?,'completed','agent',?,NOW())"
            )->execute([$txnRef, $acc['account_no'], $amount, $fee, $amount-$fee, $agentRow['id']]);

            db()->commit();
            return ['ok'=>true,'txn_ref'=>$txnRef,'amount'=>$amount,'net'=>$amount-$fee];
        } catch (Throwable $e) {
            try { db()->rollBack(); } catch (Throwable $i) {}
            return ['ok'=>false,'error'=>$e->getMessage()];
        }
    }

    // ── payBill(): دفع فاتورة ─────────────────────────────────
    public static function payBill(int $userId, string $providerCode, string $accountRef, float $amount, string $pin): array {
        if (!self::verifyPin($userId, $pin))
            return ['ok'=>false,'error'=>'invalid_pin'];
        try {
            $prov = db()->prepare("SELECT * FROM sdpay_bill_providers WHERE code=? AND is_active=1 LIMIT 1");
            $prov->execute([$providerCode]);
            $provider = $prov->fetch();
            if (!$provider) return ['ok'=>false,'error'=>'provider_not_found'];

            $acc = self::getAccount($userId);
            $fee = round($amount * $provider['commission_pct'] + $provider['fixed_fee_sdg'], 2);
            $total = $amount + $fee;

            if (($acc['balance_sdg'] - $acc['hold_sdg']) < $total)
                return ['ok'=>false,'error'=>'insufficient_balance','required'=>$total];

            db()->beginTransaction();
            db()->prepare("UPDATE sdpay_accounts SET balance_sdg=balance_sdg-? WHERE user_id=?")
               ->execute([$total, $userId]);

            $txnRef = self::generateTxnRef('BILL');
            $token  = strtoupper(bin2hex(random_bytes(8)));

            db()->prepare(
                "INSERT INTO sdpay_bill_payments
                 (user_id,txn_ref,provider_id,account_ref,amount_sdg,fee_sdg,token_received,status,paid_at)
                 VALUES (?,?,?,?,?,?,?,'completed',NOW())"
            )->execute([$userId, $txnRef, $provider['id'], $accountRef, $amount, $fee, $token]);

            db()->prepare(
                "INSERT INTO sdpay_transactions
                 (txn_ref,type,from_account,amount_sdg,fee_sdg,net_sdg,status,narration,completed_at)
                 VALUES (?,'bill',?,?,?,?,'completed',?,NOW())"
            )->execute([
                $txnRef, $acc['account_no'], $total, $fee, $amount,
                'دفع فاتورة ' . $provider['name_ar']
            ]);

            db()->commit();

            try {
                self::notifyUser($userId,'bill_paid','✅ تم دفع الفاتورة',
                    $provider['name_ar'] . ' — ' . number_format($amount) . ' SDG');
            } catch (Throwable $ignored) {}

            return ['ok'=>true,'txn_ref'=>$txnRef,'token'=>$token,'amount'=>$amount,'fee'=>$fee];
        } catch (Throwable $e) {
            try { db()->rollBack(); } catch (Throwable $i) {}
            return ['ok'=>false,'error'=>$e->getMessage()];
        }
    }

    // ── calculateFee(): حساب الرسوم ───────────────────────────
    public static function calculateFee(float $amount, string $type): float {
        if ($type === 'transfer') {
            if ($amount < 10000)  return 0;
            if ($amount < 100000) return round($amount * 0.005, 2);
            return round($amount * 0.003, 2);
        }
        return 0;
    }

    // ── generateAccountNo() ───────────────────────────────────
    private static function generateAccountNo(): string {
        do {
            $no = 'SD' . date('y') . str_pad(mt_rand(0, 99999999), 8, '0', STR_PAD_LEFT);
            $exists = db()->prepare("SELECT id FROM sdpay_accounts WHERE account_no=? LIMIT 1");
            $exists->execute([$no]);
        } while ($exists->fetch());
        return $no;
    }

    // ── generateTxnRef() ─────────────────────────────────────
    public static function generateTxnRef(string $prefix = 'TXN'): string {
        return $prefix . date('ymd') . strtoupper(bin2hex(random_bytes(6)));
    }

    // ── checkIdempotency() ───────────────────────────────────
    private static function checkIdempotency(string $key): ?array {
        try {
            $st = db()->prepare(
                "SELECT metadata FROM sdpay_transactions WHERE idempotency_key=? AND status='completed' LIMIT 1"
            );
            $st->execute([$key]);
            $row = $st->fetch();
            if ($row && $row['metadata']) {
                $cached = json_decode($row['metadata'], true);
                return array_merge(['ok'=>true,'idempotent'=>true], $cached ?? []);
            }
        } catch (Throwable $ignored) {}
        return null;
    }

    private static function saveIdempotency(string $key, array $result): void {}

    // ── notifyUser() ─────────────────────────────────────────
    private static function notifyUser(int $uid, string $type, string $title, string $body): void {
        try {
            db()->prepare(
                "INSERT INTO notifications (user_id,type,title,body,data) VALUES (?,?,?,?,?)"
            )->execute([$uid, $type, $title, $body, json_encode(['via'=>'sdpay'])]);
        } catch (Throwable $ignored) {}
    }

    // ── flagAML() ────────────────────────────────────────────
    private static function flagAML(int $userId, string $txnRef, float $amount): void {
        try {
            db()->prepare(
                "INSERT INTO sdpay_aml_reports (report_type,user_id,txn_ref,amount_sdg,reason)
                 VALUES ('CTR',?,?,?,?)"
            )->execute([$userId, $txnRef, $amount, 'معاملة تتجاوز حد التقرير التلقائي']);
        } catch (Throwable $ignored) {}
    }

    // ── awardLoyaltyPoints() ─────────────────────────────────
    public static function awardLoyaltyPoints(int $userId, int $merchantId, float $amount): void {
        try {
            $points = (int)floor($amount / 100);
            if ($points < 1) return;
            db()->prepare(
                "INSERT INTO sdpay_loyalty_points (user_id,merchant_id,points,action)
                 VALUES (?,?,?,'earned')"
            )->execute([$userId, $merchantId, $points]);
            db()->prepare(
                "UPDATE users SET achievement_points=COALESCE(achievement_points,0)+? WHERE id=?"
            )->execute([$points, $userId]);
        } catch (Throwable $ignored) {}
    }

    // ── getBalance() ─────────────────────────────────────────
    public static function getBalance(int $userId): array {
        $acc = self::getAccount($userId);
        if (!$acc) return ['balance'=>0,'hold'=>0,'available'=>0,'account_no'=>null];
        return [
            'balance'    => (float)$acc['balance_sdg'],
            'hold'       => (float)$acc['hold_sdg'],
            'available'  => round((float)$acc['balance_sdg'] - (float)$acc['hold_sdg'], 2),
            'account_no' => $acc['account_no'],
            'kyc_level'  => (int)$acc['kyc_level'],
        ];
    }

    // ── getTransactionHistory() ───────────────────────────────
    public static function getHistory(int $userId, int $limit = 20, string $type = ''): array {
        try {
            $acc = self::getAccount($userId);
            if (!$acc) return [];
            $accountNo = $acc['account_no'];
            $sql = "SELECT * FROM sdpay_transactions
                    WHERE (from_account=? OR to_account=?)";
            $params = [$accountNo, $accountNo];
            if ($type) { $sql .= " AND type=?"; $params[] = $type; }
            $sql .= " ORDER BY created_at DESC LIMIT ?";
            $params[] = $limit;
            $st = db()->prepare($sql);
            $st->execute($params);
            return $st->fetchAll();
        } catch (Throwable $e) { return []; }
    }

    // ── checkIdempotency() ────────────────────────────────────

    // ── saveIdempotency() ─────────────────────────────────────

    // ── velocityCheck() ───────────────────────────────────────
    public static function velocityCheck(string $accountNo): array {
        try {
            // 10 معاملات/دقيقة
            $st = db()->prepare(
                "SELECT COUNT(*) FROM sdpay_transactions
                 WHERE from_account=? AND created_at > DATE_SUB(NOW(),INTERVAL 1 MINUTE)"
            );
            $st->execute([$accountNo]);
            if ((int)$st->fetchColumn() >= 10)
                return ['ok'=>false,'error'=>'velocity_exceeded','message'=>'تجاوزت الحد المسموح'];

            // 50 معاملة/يوم
            $st2 = db()->prepare(
                "SELECT COUNT(*) FROM sdpay_transactions
                 WHERE from_account=? AND DATE(created_at)=CURDATE()"
            );
            $st2->execute([$accountNo]);
            if ((int)$st2->fetchColumn() >= 50)
                return ['ok'=>false,'error'=>'daily_limit_count','message'=>'تجاوزت حد المعاملات اليومي'];

            return ['ok'=>true];
        } catch (Throwable $e) { return ['ok'=>true]; }
    }

    // ── getAccountByPhone() ───────────────────────────────────
    public static function getAccountByPhone(string $phone): ?array {
        try {
            $st = db()->prepare(
                "SELECT sa.*, u.name, u.phone FROM sdpay_accounts sa
                 JOIN users u ON u.id=sa.user_id
                 WHERE u.phone=? AND sa.status='active' LIMIT 1"
            );
            $st->execute([$phone]);
            return $st->fetch() ?: null;
        } catch (Throwable $e) { return null; }
    }

    // ── lockAccount() ─────────────────────────────────────────
    public static function lockAccount(int $userId, string $reason): void {
        try {
            db()->prepare("UPDATE sdpay_accounts SET status='suspended' WHERE user_id=?")
               ->execute([$userId]);
            db()->prepare(
                "INSERT INTO audit_logs (user_id,action,details) VALUES (?,'account_locked',?)"
            )->execute([$userId, json_encode(['reason'=>$reason, 'time'=>date('c')])]);
        } catch (Throwable $ignored) {}
    }

    // ── getDashboard() ────────────────────────────────────────
    public static function getDashboard(int $userId): array {
        try {
            $acc = self::getAccount($userId);
            if (!$acc) {
                $acc = self::createAccount($userId);
                $acc = self::getAccount($userId);
            }

            // آخر 5 معاملات
            $st = db()->prepare(
                "SELECT t.*, 
                 CASE WHEN t.from_account=? THEN 'out' ELSE 'in' END as direction
                 FROM sdpay_transactions t
                 WHERE (t.from_account=? OR t.to_account=?) AND t.status='completed'
                 ORDER BY t.created_at DESC LIMIT 5"
            );
            $st->execute([$acc['account_no'], $acc['account_no'], $acc['account_no']]);
            $recent = $st->fetchAll();

            // إحصاءات الشهر
            $st2 = db()->prepare(
                "SELECT 
                  SUM(CASE WHEN to_account=? AND status='completed' THEN amount_sdg ELSE 0 END) as in_sdg,
                  SUM(CASE WHEN from_account=? AND status='completed' THEN amount_sdg ELSE 0 END) as out_sdg,
                  COUNT(CASE WHEN status='completed' THEN 1 END) as txn_count
                 FROM sdpay_transactions
                 WHERE (from_account=? OR to_account=?) AND MONTH(created_at)=MONTH(NOW())"
            );
            $st2->execute([$acc['account_no'],$acc['account_no'],$acc['account_no'],$acc['account_no']]);
            $stats = $st2->fetch();

            return [
                'account'    => $acc,
                'balance'    => (float)($acc['balance_sdg'] ?? 0),
                'hold'       => (float)($acc['hold_sdg'] ?? 0),
                'available'  => (float)($acc['balance_sdg'] ?? 0) - (float)($acc['hold_sdg'] ?? 0),
                'recent'     => $recent,
                'month_in'   => (float)($stats['in_sdg'] ?? 0),
                'month_out'  => (float)($stats['out_sdg'] ?? 0),
                'month_count'=> (int)($stats['txn_count'] ?? 0),
            ];
        } catch (Throwable $e) {
            error_log("[SudannaPayEngine::getDashboard] " . $e->getMessage());
            return ['account'=>null,'balance'=>0,'hold'=>0,'available'=>0,'recent'=>[],'month_in'=>0,'month_out'=>0,'month_count'=>0];
        }
    }

    // ── generateAccountNo() ───────────────────────────────────

    // ── flagAML() ─────────────────────────────────────────────

}
