<?php
/**
 * سودانا — BankAdapter v2 COMPLETE
 * ═══════════════════════════════════════════════════════════════
 * PRINCIPLE: NO_CUSTODY — Sudanna never holds money
 *            BANK_SOURCE — bank/EBS is source of truth
 *            DB_MIRROR   — DB reflects bank state only
 *
 * كل عملية مالية تمر عبر هذا المحرك أولاً
 * إذا قبل البنك → نحدّث DB كـ mirror
 * إذا رفض البنك → لا نحدّث DB
 * ═══════════════════════════════════════════════════════════════
 */
class BankAdapter {

    // ── الحساب الرئيسي لسودانا ─────────────────────────────────
    const SUDANNA_MAIN_ACCOUNT = '1821425';
    const DEFAULT_CURRENCY     = 'SDG';

    // ════════════════════════════════════════════════════════════
    // [1] initiateTransfer — تحويل بين حسابين
    // ════════════════════════════════════════════════════════════
    public static function initiateTransfer(
        string $fromBankAccount,
        string $toBankAccount,
        float  $amount,
        string $sdpayRef,
        string $narration = '',
        int    $institutionId = 0
    ): array {
        $traceId = self::trace('transfer', $sdpayRef);
        try {
            if ($amount <= 0) return self::fail('invalid_amount', $traceId);
            if (!$fromBankAccount || !$toBankAccount)
                return self::fail('invalid_accounts', $traceId);

            $payload = [
                'from_account' => $fromBankAccount,
                'to_account'   => $toBankAccount,
                'amount'       => $amount,
                'currency'     => self::DEFAULT_CURRENCY,
                'reference'    => $sdpayRef,
                'narration'    => $narration ?: 'Sudanna Pay Transfer',
            ];

            $response = self::httpPost('/transfers/initiate', $payload, $institutionId);
            $result   = self::parseResponse($response, $traceId, $sdpayRef, 'transfer', $amount);
            self::logBankTxn($sdpayRef, $result, 'transfer', $fromBankAccount, $toBankAccount, $amount, $institutionId);
            return $result;
        } catch (\Throwable $e) {
            error_log("[BankAdapter::initiateTransfer] $sdpayRef: ".$e->getMessage());
            return self::fail('adapter_error', $traceId, $e->getMessage());
        }
    }

    // ════════════════════════════════════════════════════════════
    // [2] confirmDeposit — تأكيد إيداع نقدي
    // ════════════════════════════════════════════════════════════
    public static function confirmDeposit(
        string $bankReceiptNo,
        float  $amount,
        string $sdpayRef,
        int    $institutionId = 0
    ): array {
        $traceId = self::trace('deposit', $sdpayRef);
        try {
            $payload = [
                'receipt_no'    => $bankReceiptNo,
                'expected_amount' => $amount,
                'reference'     => $sdpayRef,
                'account'       => self::SUDANNA_MAIN_ACCOUNT,
            ];
            $response = self::httpPost('/deposits/confirm', $payload, $institutionId);
            $result   = self::parseResponse($response, $traceId, $sdpayRef, 'deposit', $amount);
            self::logBankTxn($sdpayRef, $result, 'deposit', null, self::SUDANNA_MAIN_ACCOUNT, $amount, $institutionId);
            return $result;
        } catch (\Throwable $e) {
            error_log("[BankAdapter::confirmDeposit] $sdpayRef: ".$e->getMessage());
            return self::fail('adapter_error', $traceId, $e->getMessage());
        }
    }

    // ════════════════════════════════════════════════════════════
    // [3] initiatePayout — صرف مبلغ لحساب خارجي
    // ════════════════════════════════════════════════════════════
    public static function initiatePayout(array $payout): array {
        $traceId  = self::trace('payout', $payout['reference'] ?? '');
        $sdpayRef = $payout['reference'] ?? $traceId;
        try {
            if (empty($payout['to_account']) || ($payout['amount'] ?? 0) <= 0)
                return self::fail('invalid_payout_data', $traceId);

            $payload = [
                'from_account' => self::SUDANNA_MAIN_ACCOUNT,
                'to_account'   => $payout['to_account'],
                'to_name'      => $payout['to_name'] ?? '',
                'amount'       => $payout['amount'],
                'currency'     => self::DEFAULT_CURRENCY,
                'reference'    => $sdpayRef,
                'narration'    => $payout['narration'] ?? 'Sudanna Payout',
            ];
            $response = self::httpPost('/payouts', $payload, $payout['institution_id'] ?? 0);
            $result   = self::parseResponse($response, $traceId, $sdpayRef, 'payout', $payout['amount']);
            self::logBankTxn($sdpayRef, $result, 'payout', self::SUDANNA_MAIN_ACCOUNT, $payout['to_account'], $payout['amount'], $payout['institution_id'] ?? 0);
            return $result;
        } catch (\Throwable $e) {
            error_log("[BankAdapter::initiatePayout] $sdpayRef: ".$e->getMessage());
            return self::fail('adapter_error', $traceId, $e->getMessage());
        }
    }

    // ════════════════════════════════════════════════════════════
    // [4] checkBalance — جلب الرصيد الحقيقي من البنك
    // ════════════════════════════════════════════════════════════
    public static function checkBalance(string $bankAccountNo, int $institutionId = 0): array {
        $traceId = self::trace('balance', $bankAccountNo);
        try {
            $response = self::httpGet('/accounts/balance', ['account' => $bankAccountNo], $institutionId);
            if (self::isSandbox()) {
                return ['ok'=>true,'balance'=>0.00,'available'=>0.00,'currency'=>'SDG','sandbox'=>true,'trace_id'=>$traceId];
            }
            return [
                'ok'        => true,
                'balance'   => (float)($response['balance'] ?? 0),
                'available' => (float)($response['available'] ?? 0),
                'currency'  => $response['currency'] ?? 'SDG',
                'trace_id'  => $traceId,
            ];
        } catch (\Throwable $e) {
            error_log("[BankAdapter::checkBalance] $bankAccountNo: ".$e->getMessage());
            return self::fail('balance_check_failed', $traceId);
        }
    }

    // ════════════════════════════════════════════════════════════
    // [5] holdAmount — تجميد مبلغ (Escrow)
    // ════════════════════════════════════════════════════════════
    public static function holdAmount(
        string $bankAccountNo,
        float  $amount,
        string $sdpayRef,
        string $reason = 'escrow',
        int    $institutionId = 0
    ): array {
        $traceId = self::trace('hold', $sdpayRef);
        try {
            $payload = [
                'account'   => $bankAccountNo,
                'amount'    => $amount,
                'reference' => $sdpayRef,
                'reason'    => $reason,
            ];
            $response = self::httpPost('/holds/create', $payload, $institutionId);
            $result   = self::parseResponse($response, $traceId, $sdpayRef, 'hold', $amount);
            self::logBankTxn($sdpayRef, $result, 'hold', $bankAccountNo, null, $amount, $institutionId);
            return $result;
        } catch (\Throwable $e) {
            error_log("[BankAdapter::holdAmount] $sdpayRef: ".$e->getMessage());
            return self::fail('hold_failed', $traceId, $e->getMessage());
        }
    }

    // ════════════════════════════════════════════════════════════
    // [6] releaseHold — تحرير مبلغ مجمّد
    // ════════════════════════════════════════════════════════════
    public static function releaseHold(string $holdRef, string $releaseTo = '', int $institutionId = 0): array {
        $traceId = self::trace('release', $holdRef);
        try {
            $payload = ['hold_reference' => $holdRef, 'release_to' => $releaseTo];
            $response = self::httpPost('/holds/release', $payload, $institutionId);
            return self::parseResponse($response, $traceId, $holdRef, 'release', 0);
        } catch (\Throwable $e) {
            error_log("[BankAdapter::releaseHold] $holdRef: ".$e->getMessage());
            return self::fail('release_failed', $traceId, $e->getMessage());
        }
    }

    // ════════════════════════════════════════════════════════════
    // [7] reverseTransaction — عكس معاملة في البنك
    // ════════════════════════════════════════════════════════════
    public static function reverseTransaction(string $bankTxnId, string $reason = 'user_request', int $institutionId = 0): array {
        $traceId = self::trace('reverse', $bankTxnId);
        try {
            $payload = ['bank_txn_id' => $bankTxnId, 'reason' => $reason];
            $response = self::httpPost('/transactions/reverse', $payload, $institutionId);
            return self::parseResponse($response, $traceId, $bankTxnId, 'reversal', 0);
        } catch (\Throwable $e) {
            error_log("[BankAdapter::reverseTransaction] $bankTxnId: ".$e->getMessage());
            return self::fail('reversal_failed', $traceId, $e->getMessage());
        }
    }

    // ════════════════════════════════════════════════════════════
    // [8] getTransactionStatus — حالة معاملة في البنك
    // ════════════════════════════════════════════════════════════
    public static function getTransactionStatus(string $bankTxnId, int $institutionId = 0): array {
        $traceId = self::trace('status', $bankTxnId);
        try {
            $response = self::httpGet('/transactions/status', ['txn_id' => $bankTxnId], $institutionId);
            if (self::isSandbox()) {
                return ['ok'=>true,'status'=>'completed','bank_txn_id'=>$bankTxnId,'sandbox'=>true,'trace_id'=>$traceId];
            }
            return [
                'ok'          => true,
                'status'      => $response['status'] ?? 'unknown',
                'bank_txn_id' => $bankTxnId,
                'amount'      => (float)($response['amount'] ?? 0),
                'completed_at'=> $response['completed_at'] ?? null,
                'trace_id'    => $traceId,
            ];
        } catch (\Throwable $e) {
            error_log("[BankAdapter::getTransactionStatus] $bankTxnId: ".$e->getMessage());
            return self::fail('status_check_failed', $traceId);
        }
    }

    // ════════════════════════════════════════════════════════════
    // [9] validateBankAccount — التحقق من صحة حساب بنكي
    // ════════════════════════════════════════════════════════════
    public static function validateBankAccount(string $accountNo, int $institutionId = 0): array {
        $traceId = self::trace('validate', $accountNo);
        try {
            $response = self::httpGet('/accounts/validate', ['account' => $accountNo], $institutionId);
            if (self::isSandbox()) {
                return ['ok'=>true,'valid'=>true,'account_name'=>'Sandbox Account','sandbox'=>true,'trace_id'=>$traceId];
            }
            return [
                'ok'           => true,
                'valid'        => !empty($response['valid']),
                'account_name' => $response['account_name'] ?? '',
                'account_type' => $response['account_type'] ?? '',
                'trace_id'     => $traceId,
            ];
        } catch (\Throwable $e) {
            error_log("[BankAdapter::validateBankAccount] $accountNo: ".$e->getMessage());
            return ['ok'=>false,'valid'=>false,'trace_id'=>$traceId];
        }
    }

    // ════════════════════════════════════════════════════════════
    // [10] confirmExternalCredit — تأكيد إيداع استثماري خارجي
    // ════════════════════════════════════════════════════════════
    public static function confirmExternalCredit(string $bankRef, float $expectedAmount, int $projectId, int $institutionId = 0): array {
        $traceId = self::trace('ext_credit', $bankRef);
        try {
            $payload = [
                'bank_reference'  => $bankRef,
                'expected_amount' => $expectedAmount,
                'project_id'      => $projectId,
                'account'         => self::SUDANNA_MAIN_ACCOUNT,
            ];
            $response = self::httpPost('/credits/confirm-external', $payload, $institutionId);
            $result   = self::parseResponse($response, $traceId, $bankRef, 'deposit', $expectedAmount);
            self::logBankTxn($bankRef, $result, 'deposit', null, self::SUDANNA_MAIN_ACCOUNT, $expectedAmount, $institutionId);
            return $result;
        } catch (\Throwable $e) {
            error_log("[BankAdapter::confirmExternalCredit] $bankRef: ".$e->getMessage());
            return self::fail('external_credit_failed', $traceId, $e->getMessage());
        }
    }

    // ════════════════════════════════════════════════════════════
    // [11] verifyTransfer — التحقق من تحويل وارد
    // ════════════════════════════════════════════════════════════
    public static function verifyTransfer(string $referenceCode, int $institutionId = 0): ?array {
        try {
            $response = self::httpGet('/transfers/verify', [
                'reference' => $referenceCode,
                'account'   => self::SUDANNA_MAIN_ACCOUNT,
            ], $institutionId);
            if (self::isSandbox()) return null;
            if (empty($response['confirmed'])) return null;
            return [
                'confirmed'      => true,
                'bank_ref'       => $response['txn_id'] ?? null,
                'amount'         => (float)($response['amount'] ?? 0),
                'currency'       => $response['currency'] ?? 'SDG',
                'transferred_at' => $response['timestamp'] ?? null,
                'sender_name'    => $response['sender'] ?? null,
            ];
        } catch (\Throwable $e) {
            error_log("[BankAdapter::verifyTransfer] ".$e->getMessage());
            return null;
        }
    }

    // ════════════════════════════════════════════════════════════
    // [12] getStatement — كشف حساب للتسوية
    // ════════════════════════════════════════════════════════════
    public static function getStatement(string $from, string $to, int $institutionId = 0): array {
        try {
            return self::httpGet('/statements', [
                'account' => self::SUDANNA_MAIN_ACCOUNT,
                'from'    => $from,
                'to'      => $to,
            ], $institutionId);
        } catch (\Throwable $e) {
            error_log("[BankAdapter::getStatement] ".$e->getMessage());
            return [];
        }
    }

    // ════════════════════════════════════════════════════════════
    // [13] getInstitutions — قائمة المؤسسات الشريكة
    // ════════════════════════════════════════════════════════════
    public static function getInstitutions(): array {
        try {
            $st = db()->prepare("SELECT * FROM financial_institutions WHERE is_active=1 ORDER BY priority_order");
            $st->execute();
            return $st->fetchAll();
        } catch (\Throwable $e) { return []; }
    }

    // ════════════════════════════════════════════════════════════
    // [14] processWebhook — معالجة webhook وارد من البنك
    // ════════════════════════════════════════════════════════════
    public static function processWebhook(array $payload, string $signature, int $institutionId): array {
        // تحقق من التوقيع
        $expectedSig = hash_hmac('sha256', json_encode($payload), defined('BANK_WEBHOOK_SECRET') ? BANK_WEBHOOK_SECRET : '');
        $isVerified  = hash_equals($expectedSig, $signature);

        // سجّل الـ webhook
        try {
            db()->prepare(
                "INSERT INTO bank_webhooks (institution_id,event_type,bank_txn_id,sdpay_ref,payload,signature,is_verified)
                 VALUES (?,?,?,?,?,?,?)"
            )->execute([
                $institutionId,
                $payload['event'] ?? 'unknown',
                $payload['bank_txn_id'] ?? null,
                $payload['reference'] ?? null,
                json_encode($payload),
                $signature,
                (int)$isVerified,
            ]);
        } catch (\Throwable $ignored) {}

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

        // معالجة حسب نوع الحدث
        $event = $payload['event'] ?? '';
        switch ($event) {
            case 'transfer.completed':
            case 'deposit.confirmed':
            case 'payout.completed':
                // حدّث sdpay_transactions
                if (!empty($payload['reference'])) {
                    try {
                        db()->prepare(
                            "UPDATE sdpay_transactions SET bank_txn_id=?, bank_confirmed_at=NOW(), bank_response_code='00'
                             WHERE txn_ref=? AND status='processing'"
                        )->execute([$payload['bank_txn_id'] ?? null, $payload['reference']]);

                        db()->prepare(
                            "UPDATE sdpay_transactions SET status='completed', completed_at=NOW()
                             WHERE txn_ref=? AND status='processing'"
                        )->execute([$payload['reference']]);
                    } catch (\Throwable $ignored) {}
                }
                return ['ok'=>true,'processed'=>true,'event'=>$event];

            case 'transfer.failed':
            case 'payout.failed':
                if (!empty($payload['reference'])) {
                    try {
                        db()->prepare(
                            "UPDATE sdpay_transactions SET status='failed', failed_reason=?, bank_response_code=?
                             WHERE txn_ref=? AND status='processing'"
                        )->execute([
                            $payload['failure_reason'] ?? 'bank_rejected',
                            $payload['response_code'] ?? 'XX',
                            $payload['reference']
                        ]);
                    } catch (\Throwable $ignored) {}
                }
                return ['ok'=>true,'processed'=>true,'event'=>$event];
        }
        return ['ok'=>true,'processed'=>false,'event'=>$event];
    }

    // ════════════════════════════════════════════════════════════
    // HELPERS
    // ════════════════════════════════════════════════════════════
    public static function isSandbox(): bool {
        return !defined('BANK_INTEGRATION_ENABLED') || !BANK_INTEGRATION_ENABLED
            || (defined('SANDBOX_MODE') && SANDBOX_MODE);
    }

    private static function parseResponse(array $response, string $traceId, string $ref, string $type, float $amount): array {
        if (self::isSandbox()) {
            return [
                'ok'          => true,
                'confirmed'   => true,
                'bank_txn_id' => 'SANDBOX_' . strtoupper(substr(md5($ref), 0, 10)),
                'status'      => 'completed',
                'amount'      => $amount,
                'sandbox'     => true,
                'trace_id'    => $traceId,
            ];
        }
        $ok = !empty($response['status']) && in_array($response['status'], ['accepted','completed','confirmed']);
        return [
            'ok'          => $ok,
            'confirmed'   => $ok,
            'bank_txn_id' => $response['txn_id'] ?? null,
            'status'      => $response['status'] ?? 'unknown',
            'amount'      => $amount,
            'error_code'  => $response['error_code'] ?? null,
            'error_msg'   => $response['error_message'] ?? null,
            'trace_id'    => $traceId,
        ];
    }

    private static function logBankTxn(string $sdpayRef, array $result, string $type, ?string $from, ?string $to, float $amount, int $institutionId): void {
        try {
            db()->prepare(
                "INSERT INTO bank_transactions
                 (bank_txn_id,institution_id,sdpay_txn_ref,txn_type,from_bank_account,to_bank_account,
                  amount_sdg,bank_status,bank_response_code,initiated_at)
                 VALUES (?,?,?,?,?,?,?,?,?,NOW())
                 ON DUPLICATE KEY UPDATE bank_status=VALUES(bank_status)"
            )->execute([
                $result['bank_txn_id'] ?? ('PENDING_'.strtoupper(substr(md5($sdpayRef),0,8))),
                $institutionId,
                $sdpayRef, $type, $from, $to, $amount,
                $result['confirmed'] ? 'completed' : 'failed',
                $result['error_code'] ?? ($result['confirmed'] ? '00' : 'XX'),
            ]);
        } catch (\Throwable $ignored) {}
    }

    private static function trace(string $op, string $ref): string {
        return strtoupper($op[0]) . date('YmdHis') . '_' . substr(md5($ref . uniqid()), 0, 6);
    }

    private static function fail(string $code, string $traceId, string $msg = ''): array {
        return ['ok'=>false,'confirmed'=>false,'error_code'=>$code,'error_msg'=>$msg,'trace_id'=>$traceId];
    }

    private static function getInstitutionConfig(int $institutionId): array {
        if (!$institutionId) return [];
        try {
            $st = db()->prepare(
                "SELECT fi.api_url, fi.sandbox_api_url, fi.is_sandbox, iac.api_key_enc, iac.merchant_id
                 FROM financial_institutions fi
                 LEFT JOIN institution_api_configs iac ON iac.institution_id=fi.id AND iac.env=IF(fi.is_sandbox,'sandbox','production')
                 WHERE fi.id=? AND fi.is_active=1 LIMIT 1"
            );
            $st->execute([$institutionId]);
            return $st->fetch() ?: [];
        } catch (\Throwable $e) { return []; }
    }

    private static function httpGet(string $path, array $params = [], int $institutionId = 0): array {
        if (self::isSandbox()) {
            error_log("[BankAdapter] SANDBOX GET $path");
            return ['sandbox'=>true];
        }
        $config = self::getInstitutionConfig($institutionId);
        $baseUrl = $config['api_url'] ?? (defined('BANK_API_URL') ? BANK_API_URL : '');
        if (!$baseUrl) return ['sandbox'=>true];
        $url = $baseUrl . $path . ($params ? '?' . http_build_query($params) : '');
        $ch  = curl_init($url);
        $key = $config['api_key_enc'] ?? (defined('BANK_API_KEY') ? BANK_API_KEY : '');
        curl_setopt_array($ch, [
            CURLOPT_RETURNTRANSFER => true,
            CURLOPT_TIMEOUT        => 10,
            CURLOPT_HTTPHEADER     => ["Authorization: Bearer $key", 'Content-Type: application/json'],
        ]);
        $resp = curl_exec($ch); curl_close($ch);
        return json_decode($resp ?: '{}', true) ?: [];
    }

    private static function httpPost(string $path, array $body, int $institutionId = 0): array {
        if (self::isSandbox()) {
            error_log("[BankAdapter] SANDBOX POST $path");
            return ['sandbox'=>true, 'status'=>'sandbox_accepted', 'txn_id'=>'SANDBOX_'.strtoupper(substr(md5(uniqid()),0,10))];
        }
        $config  = self::getInstitutionConfig($institutionId);
        $baseUrl = $config['api_url'] ?? (defined('BANK_API_URL') ? BANK_API_URL : '');
        if (!$baseUrl) return ['sandbox'=>true, 'status'=>'sandbox_accepted', 'txn_id'=>'SANDBOX_'.uniqid()];
        $url = $baseUrl . $path;
        $key = $config['api_key_enc'] ?? (defined('BANK_API_KEY') ? BANK_API_KEY : '');
        $ch  = curl_init($url);
        curl_setopt_array($ch, [
            CURLOPT_POST           => true,
            CURLOPT_POSTFIELDS     => json_encode($body),
            CURLOPT_RETURNTRANSFER => true,
            CURLOPT_TIMEOUT        => 15,
            CURLOPT_HTTPHEADER     => ["Authorization: Bearer $key", 'Content-Type: application/json'],
        ]);
        $resp = curl_exec($ch); curl_close($ch);
        return json_decode($resp ?: '{}', true) ?: ['status'=>'error'];
    }
}
