<?php
/**
 * Withdrawal-only multi-gateway router.
 *
 * Deposit functions are intentionally not used or modified here. The router:
 *  - reads enabled payout gateways in failover_priority order;
 *  - checks merchant balance when a documented/implemented balance endpoint exists;
 *  - reserves the user's balance once;
 *  - tries the next gateway only after a definitive synchronous rejection;
 *  - never retries another gateway after an ambiguous timeout/response, preventing double payout;
 *  - refunds the user exactly once if every gateway definitively fails.
 */

if (!defined('WCB_WITHDRAW_ROUTER_LOADED')) {
    define('WCB_WITHDRAW_ROUTER_LOADED', true);
}

if (!function_exists('wcb_router_json')) {
    function wcb_router_json($data) {
        if (function_exists('propay_json')) { return propay_json($data); }
        return json_encode($data, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE);
    }
}

if (!function_exists('wcb_router_safe_status')) {
    function wcb_router_safe_status($status, $fallback = 'unknown') {
        $status = strtolower(trim((string)$status));
        $status = preg_replace('/[^a-z0-9_\-]/', '_', $status);
        return $status !== '' ? substr($status, 0, 60) : $fallback;
    }
}

if (!function_exists('wcb_router_update_health')) {
    function wcb_router_update_health($conn, $provider, $status, $message = '') {
        if (!$conn || !function_exists('propay_column_exists')) { return; }
        if (!propay_column_exists($conn, 'payment_gateway_settings', 'last_health_status')) { return; }
        $provider = strtolower(trim((string)$provider));
        $status = substr(strtolower(trim((string)$status)), 0, 20);
        $message = trim((string)$message);
        $stmt = $conn->prepare("UPDATE payment_gateway_settings SET last_health_status=?, last_health_checked_at=NOW(), last_error=? WHERE provider=?");
        if ($stmt) {
            $stmt->bind_param('sss', $status, $message, $provider);
            $stmt->execute();
            $stmt->close();
        }
    }
}

if (!function_exists('wcb_router_extract_balance')) {
    function wcb_router_extract_balance($data) {
        if (!is_array($data)) { return null; }
        $keys = array('available_balance', 'availableBalance', 'merchant_balance', 'merchantBalance', 'current_balance', 'currentBalance', 'wallet_balance', 'walletBalance', 'balance');
        foreach ($keys as $key) {
            if (array_key_exists($key, $data) && is_numeric($data[$key])) {
                return round((float)$data[$key], 2);
            }
        }
        foreach (array('data', 'result', 'merchant', 'account') as $container) {
            if (isset($data[$container]) && is_array($data[$container])) {
                $found = wcb_router_extract_balance($data[$container]);
                if ($found !== null) { return $found; }
            }
        }
        return null;
    }
}

if (!function_exists('wcb_router_reason_is_insufficient')) {
    function wcb_router_reason_is_insufficient($reason) {
        $reason = strtolower((string)$reason);
        return strpos($reason, 'insufficient') !== false || strpos($reason, 'not enough balance') !== false || strpos($reason, 'balance is low') !== false;
    }
}

if (!function_exists('wcb_router_gateway_supports_method')) {
    function wcb_router_gateway_supports_method($provider, $method) {
        $provider = strtolower(trim((string)$provider));
        $method = strtolower(trim((string)$method));
        if ($provider === 'propay') { return in_array($method, array('bkash', 'nagad'), true); }
        if ($provider === 'akpay') { return in_array($method, array('bkash', 'nagad', 'rocket', 'upi'), true); }
        return false;
    }
}

if (!function_exists('wcb_router_gateway_rows')) {
    function wcb_router_gateway_rows($conn, $method) {
        if (function_exists('propay_ensure_schema')) { propay_ensure_schema($conn); }
        if (function_exists('akpay_ensure_schema')) { akpay_ensure_schema($conn); }
        $rows = array();
        $sql = "SELECT provider, merchant_code, secret_code, api_base_url, is_enabled, failover_priority
                FROM payment_gateway_settings
                WHERE provider IN ('propay','akpay') AND is_enabled=1
                ORDER BY failover_priority ASC, id ASC";
        $res = @$conn->query($sql);
        if ($res) {
            while ($row = $res->fetch_assoc()) {
                $provider = strtolower(trim((string)($row['provider'] ?? '')));
                if (!wcb_router_gateway_supports_method($provider, $method)) { continue; }
                $merchant = trim((string)($row['merchant_code'] ?? ''));
                $secret = trim((string)($row['secret_code'] ?? ''));
                $base = trim((string)($row['api_base_url'] ?? ''));
                if ($provider === 'propay' && $secret === '') { continue; }
                if ($provider === 'akpay' && ($merchant === '' || $secret === '' || $base === '')) { continue; }
                $row['provider'] = $provider;
                $row['failover_priority'] = max(1, intval($row['failover_priority'] ?? 1));
                $rows[] = $row;
            }
        }
        return $rows;
    }
}

if (!function_exists('wcb_router_propay_preflight')) {
    function wcb_router_propay_preflight($conn, $amount) {
        if (!function_exists('propay_check_merchant_balance')) {
            return array('eligible' => true, 'balance_known' => false, 'message' => 'Balance check is unavailable; payout submission will be verified by the gateway response.');
        }
        $result = propay_check_merchant_balance($conn);
        $decoded = is_array($result['data'] ?? null) ? $result['data'] : array();
        $balance = wcb_router_extract_balance($decoded);
        if (!empty($result['success'])) {
            wcb_router_update_health($conn, 'propay', 'up', $balance !== null ? ('Available payout balance: ' . number_format($balance, 2, '.', '')) : 'Balance endpoint responded successfully.');
            if ($balance !== null && $balance + 0.0001 < (float)$amount) {
                return array('eligible' => false, 'definitive' => true, 'balance_known' => true, 'balance' => $balance, 'message' => 'Insufficient merchant balance.');
            }
            return array('eligible' => true, 'balance_known' => $balance !== null, 'balance' => $balance, 'message' => 'Merchant balance check passed.');
        }
        $message = trim((string)($result['message'] ?? 'Merchant balance check failed.'));
        // A failed read-only balance endpoint is not proof that the payout endpoint is down.
        // Keep the gateway eligible and let the definitive payout response decide failover.
        wcb_router_update_health($conn, 'propay', 'degraded', $message);
        return array('eligible' => true, 'balance_known' => false, 'message' => $message);
    }
}

if (!function_exists('wcb_router_preflight')) {
    function wcb_router_preflight($conn, $gateway, $amount) {
        $provider = strtolower((string)($gateway['provider'] ?? ''));
        if ($provider === 'propay') { return wcb_router_propay_preflight($conn, $amount); }
        if ($provider === 'akpay') {
            // AKPay's documented v1 API exposes collect/order/payout, but no merchant-balance endpoint.
            // Balance is therefore determined from a definitive payout response (code 9 = insufficient balance).
            return array('eligible' => true, 'balance_known' => false, 'message' => 'Merchant balance will be confirmed by the payout response.');
        }
        return array('eligible' => false, 'definitive' => true, 'message' => 'Unsupported payout gateway.');
    }
}

if (!function_exists('wcb_router_reserve_user_balance')) {
    function wcb_router_reserve_user_balance($conn, $uid, $amount, $wallet, $methodKey) {
        $uid = intval($uid);
        $amount = round((float)$amount, 2);
        $accountNo = trim((string)($wallet['wallet_number'] ?? ''));
        $walletId = intval($wallet['id'] ?? 0);
        $rootRef = function_exists('propay_make_order_no') ? propay_make_order_no('WR', $uid) : ('WR' . date('ymdHis') . $uid . mt_rand(1000, 9999));
        $historyMethod = 'Auto ' . strtoupper($methodKey) . ' (' . $accountNo . ')';
        $note = 'Automatic multi-gateway withdrawal routing started.';

        $conn->begin_transaction();
        try {
            $stmtUser = $conn->prepare("SELECT balance, COALESCE(turnover_target,0) AS turnover_target, COALESCE(turnover_completed,0) AS turnover_completed FROM users WHERE id=? LIMIT 1 FOR UPDATE");
            if (!$stmtUser) { throw new RuntimeException('Unable to lock user balance.'); }
            $stmtUser->bind_param('i', $uid);
            if (!$stmtUser->execute()) { throw new RuntimeException('Unable to read user balance.'); }
            $userRes = $stmtUser->get_result();
            if (!$userRes || $userRes->num_rows === 0) { throw new RuntimeException('User account was not found.'); }
            $user = $userRes->fetch_assoc();
            $stmtUser->close();

            $remaining = max(0, (float)$user['turnover_target'] - (float)$user['turnover_completed']);
            if ($remaining > 0.01) {
                $conn->rollback();
                return array('success' => false, 'message' => 'Required turnover is not complete. Remaining Turnover: ৳' . number_format($remaining, 2));
            }
            if ((float)$user['balance'] < $amount) {
                $conn->rollback();
                return array('success' => false, 'message' => 'Insufficient withdrawable balance.');
            }

            $stmtDeduct = $conn->prepare("UPDATE users SET balance=balance-? WHERE id=? AND balance>=?");
            if (!$stmtDeduct) { throw new RuntimeException('Unable to reserve user balance.'); }
            $stmtDeduct->bind_param('did', $amount, $uid, $amount);
            $stmtDeduct->execute();
            if ($stmtDeduct->affected_rows < 1) {
                $stmtDeduct->close();
                $conn->rollback();
                return array('success' => false, 'message' => 'Insufficient withdrawable balance.');
            }
            $stmtDeduct->close();

            $stmtTx = $conn->prepare("INSERT INTO transactions_fake (user_id, type, amount, method, status, wallet_number, withdraw_wallet_id, withdraw_method_code, withdraw_pin_verified, transaction_id, order_sn, agent_id, admin_note, created_at) VALUES (?, 'withdraw', ?, ?, 'processing', ?, ?, ?, 1, ?, ?, 0, ?, NOW())");
            if (!$stmtTx) { throw new RuntimeException('Unable to create withdrawal record.'); }
            $stmtTx->bind_param('idssissss', $uid, $amount, $historyMethod, $accountNo, $walletId, $methodKey, $rootRef, $rootRef, $note);
            if (!$stmtTx->execute()) { throw new RuntimeException('Unable to create withdrawal record.'); }
            $txId = intval($stmtTx->insert_id);
            $stmtTx->close();
            $conn->commit();
            return array('success' => true, 'transaction_id' => $txId, 'root_ref' => $rootRef);
        } catch (Throwable $e) {
            $conn->rollback();
            error_log('Withdrawal reserve failed: ' . $e->getMessage());
            return array('success' => false, 'message' => $e->getMessage() ?: 'Unable to start automatic withdrawal.');
        }
    }
}

if (!function_exists('wcb_router_insert_order')) {
    function wcb_router_insert_order($conn, $txId, $orderNo, $uid, $provider, $method, $accountNo, $amount, $rawRequest) {
        $stmt = $conn->prepare("INSERT INTO payment_gateway_orders (transaction_id, order_no, user_id, gateway, type, method, account_number, amount, status, gateway_status, raw_request, created_at) VALUES (?, ?, ?, ?, 'withdraw', ?, ?, ?, 'processing', 'submitting', ?, NOW())");
        if (!$stmt) { return false; }
        $stmt->bind_param('isisssds', $txId, $orderNo, $uid, $provider, $method, $accountNo, $amount, $rawRequest);
        $ok = $stmt->execute();
        $stmt->close();
        if ($ok && function_exists('propay_column_exists') && propay_column_exists($conn, 'payment_gateway_orders', 'provider')) {
            $up = $conn->prepare("UPDATE payment_gateway_orders SET provider=? WHERE order_no=?");
            if ($up) { $up->bind_param('ss', $provider, $orderNo); $up->execute(); $up->close(); }
        }
        return $ok;
    }
}

if (!function_exists('wcb_router_mark_attempt_failed')) {
    function wcb_router_mark_attempt_failed($conn, $orderNo, $provider, $gatewayStatus, $rawResponse, $reason) {
        $gatewayStatus = wcb_router_safe_status($gatewayStatus, 'rejected');
        $stmt = $conn->prepare("UPDATE payment_gateway_orders SET status='failed', gateway_status=?, raw_response=?, callback_payload=?, updated_at=NOW() WHERE order_no=? AND gateway=? AND type='withdraw'");
        if ($stmt) {
            $reasonJson = wcb_router_json(array('reason' => (string)$reason));
            $stmt->bind_param('sssss', $gatewayStatus, $rawResponse, $reasonJson, $orderNo, $provider);
            $stmt->execute();
            $stmt->close();
        }
        $health = wcb_router_reason_is_insufficient($reason) ? 'up' : 'down';
        wcb_router_update_health($conn, $provider, $health, (string)$reason);
    }
}

if (!function_exists('wcb_router_mark_attempt_unknown')) {
    function wcb_router_mark_attempt_unknown($conn, $txId, $orderNo, $provider, $rawResponse, $reason, $historyMethod) {
        $stmt = $conn->prepare("UPDATE payment_gateway_orders SET status='processing', gateway_status='submit_unknown', raw_response=?, updated_at=NOW() WHERE order_no=? AND gateway=? AND type='withdraw'");
        if ($stmt) { $stmt->bind_param('sss', $rawResponse, $orderNo, $provider); $stmt->execute(); $stmt->close(); }
        $note = strtoupper($provider) . ' payout submission result is unknown. Automatic failover was stopped to prevent a duplicate payout. ' . $reason;
        $stmtTx = $conn->prepare("UPDATE transactions_fake SET method=?, admin_note=? WHERE id=? AND status='processing'");
        if ($stmtTx) { $stmtTx->bind_param('ssi', $historyMethod, $note, $txId); $stmtTx->execute(); $stmtTx->close(); }
        wcb_router_update_health($conn, $provider, 'degraded', $reason);
    }
}

if (!function_exists('wcb_router_update_selected_transaction')) {
    function wcb_router_update_selected_transaction($conn, $txId, $historyMethod, $gatewayOrderNo, $provider, $gatewayStatus) {
        $note = strtoupper($provider) . ' automatic payout submitted. Gateway status: ' . $gatewayStatus;
        $stmt = $conn->prepare("UPDATE transactions_fake SET method=?, lg_order_sn=?, admin_note=? WHERE id=? AND status='processing'");
        if ($stmt) { $stmt->bind_param('sssi', $historyMethod, $gatewayOrderNo, $note, $txId); $stmt->execute(); $stmt->close(); }
    }
}

if (!function_exists('wcb_router_attempt_propay')) {
    function wcb_router_attempt_propay($conn, $txId, $uid, $amount, $wallet, $methodKey) {
        $settings = propay_get_settings($conn);
        $secret = trim((string)($settings['secret_code'] ?? ''));
        $accountNo = preg_replace('/\D+/', '', (string)($wallet['wallet_number'] ?? ''));
        $orderNo = propay_make_order_no('WP', $uid);
        $historyMethod = 'ProPay ' . propay_method_label($methodKey) . ' (' . $accountNo . ')';
        $apiFields = array(
            'api_key' => $secret,
            'amount' => propay_format_amount($amount),
            'bank_code' => propay_bank_code($methodKey),
            'account_number' => $accountNo
        );
        $log = $apiFields;
        $log['api_key'] = '***hidden***';
        $rawRequest = wcb_router_json($log);
        if (!wcb_router_insert_order($conn, $txId, $orderNo, $uid, 'propay', $methodKey, $accountNo, $amount, $rawRequest)) {
            return array('accepted' => false, 'safe_failure' => true, 'reason' => 'Unable to create ProPay payout attempt.');
        }

        $http = propay_http_post(propay_withdraw_endpoint(), $apiFields, 35);
        $decoded = json_decode((string)$http['body'], true);
        $rawResponse = (string)$http['body'];
        if (!$http['success'] || !is_array($decoded)) {
            $reason = trim((string)($http['error'] ?? '')) ?: 'Invalid or missing response from ProPay.';
            wcb_router_mark_attempt_unknown($conn, $txId, $orderNo, 'propay', $rawResponse, $reason, $historyMethod);
            return array('accepted' => true, 'unknown' => true, 'provider' => 'propay', 'order_no' => $orderNo, 'message' => 'Withdrawal is processing while ProPay confirmation is pending.');
        }

        $topStatus = strtolower(trim((string)($decoded['status'] ?? '')));
        $gatewayOrderNo = trim((string)($decoded['order_no'] ?? ''));
        if ($topStatus !== 'success') {
            $reason = trim((string)($decoded['message'] ?? ($decoded['error'] ?? 'ProPay rejected the withdrawal.')));
            if ($reason === '') { $reason = 'ProPay rejected the withdrawal.'; }
            wcb_router_mark_attempt_failed($conn, $orderNo, 'propay', $topStatus ?: 'rejected', $rawResponse, $reason);
            return array('accepted' => false, 'safe_failure' => true, 'provider' => 'propay', 'reason' => $reason);
        }
        if ($gatewayOrderNo === '') {
            $reason = 'ProPay returned success without a gateway order number.';
            wcb_router_mark_attempt_unknown($conn, $txId, $orderNo, 'propay', $rawResponse, $reason, $historyMethod);
            return array('accepted' => true, 'unknown' => true, 'provider' => 'propay', 'order_no' => $orderNo, 'message' => 'Withdrawal is processing while ProPay confirmation is pending.');
        }

        $gatewayStatus = propay_extract_withdraw_status($decoded);
        if ($gatewayStatus === '') { $gatewayStatus = 'payouting'; }
        if (propay_status_is_failed($gatewayStatus)) {
            $reason = trim((string)($decoded['message'] ?? 'ProPay payout failed.'));
            wcb_router_mark_attempt_failed($conn, $orderNo, 'propay', $gatewayStatus, $rawResponse, $reason);
            return array('accepted' => false, 'safe_failure' => true, 'provider' => 'propay', 'reason' => $reason);
        }

        $stmt = $conn->prepare("UPDATE payment_gateway_orders SET gateway_order_no=?, gateway_status=?, raw_response=?, updated_at=NOW() WHERE order_no=? AND gateway='propay' AND type='withdraw'");
        if ($stmt) { $stmt->bind_param('ssss', $gatewayOrderNo, $gatewayStatus, $rawResponse, $orderNo); $stmt->execute(); $stmt->close(); }
        wcb_router_update_selected_transaction($conn, $txId, $historyMethod, $gatewayOrderNo, 'propay', $gatewayStatus);
        wcb_router_update_health($conn, 'propay', 'up', 'Payout accepted by gateway.');
        if (propay_status_is_success($gatewayStatus)) { propay_mark_withdraw_success($conn, $orderNo, $gatewayStatus, $rawResponse); }
        return array('accepted' => true, 'provider' => 'propay', 'order_no' => $gatewayOrderNo, 'gateway_status' => $gatewayStatus, 'message' => 'Withdrawal submitted successfully through ProPay.');
    }
}

if (!function_exists('wcb_router_akpay_status_name')) {
    function wcb_router_akpay_status_name($status) {
        $status = strtolower(trim((string)$status));
        if ($status === '1' || $status === 'success' || $status === 'completed') { return 'success'; }
        if ($status === '2' || $status === 'failed' || $status === 'fail' || $status === 'rejected') { return 'failed'; }
        if ($status === '0' || $status === 'pending' || $status === 'processing' || $status === 'payouting') { return 'processing'; }
        return $status !== '' ? $status : 'processing';
    }
}

if (!function_exists('wcb_router_akpay_reconcile_submit')) {
    function wcb_router_akpay_reconcile_submit($conn, $txId, $orderNo, $uid, $amount, $methodKey, $accountNo, $historyMethod, $originalRaw) {
        $settings = akpay_get_settings($conn);
        $queryPayload = array('mchId' => trim((string)$settings['merchant_code']), 'out_trade_no' => $orderNo);
        $queryPayload['sign'] = akpay_generate_sign($queryPayload, trim((string)$settings['secret_code']));
        $http = akpay_http_post_form(akpay_api_url($conn, '/v1/order'), $queryPayload, 20);
        $decoded = json_decode((string)$http['body'], true);
        if ($http['success'] && is_array($decoded)) {
            $code = intval($decoded['code'] ?? -1);
            if ($code === 7) {
                $reason = trim((string)($decoded['message'] ?? $decoded['msg'] ?? 'AKPay order was not created.'));
                wcb_router_mark_attempt_failed($conn, $orderNo, 'akpay', 'not_found', (string)$http['body'], $reason);
                return array('accepted' => false, 'safe_failure' => true, 'provider' => 'akpay', 'reason' => $reason);
            }
            if (in_array($code, array(0, 200), true) && is_array($decoded['data'] ?? null)) {
                $data = $decoded['data'];
                $gatewayStatus = wcb_router_akpay_status_name($data['status'] ?? 'processing');
                $gatewayTxn = trim((string)($data['transaction_id'] ?? ''));
                $raw = wcb_router_json(array('submit_response' => $originalRaw, 'status_check' => $decoded));
                if ($gatewayStatus === 'failed') {
                    $reason = trim((string)($decoded['message'] ?? $decoded['msg'] ?? 'AKPay payout failed.'));
                    wcb_router_mark_attempt_failed($conn, $orderNo, 'akpay', 'failed', $raw, $reason);
                    return array('accepted' => false, 'safe_failure' => true, 'provider' => 'akpay', 'reason' => $reason);
                }
                $stmt = $conn->prepare("UPDATE payment_gateway_orders SET gateway_order_no=?, gateway_status=?, raw_response=?, updated_at=NOW() WHERE order_no=? AND gateway='akpay' AND type='withdraw'");
                if ($stmt) { $stmt->bind_param('ssss', $gatewayTxn, $gatewayStatus, $raw, $orderNo); $stmt->execute(); $stmt->close(); }
                wcb_router_update_selected_transaction($conn, $txId, $historyMethod, $gatewayTxn, 'akpay', $gatewayStatus);
                wcb_router_update_health($conn, 'akpay', 'up', 'Payout order confirmed by status check.');
                if ($gatewayStatus === 'success') { akpay_mark_withdraw_success($conn, $orderNo, $gatewayStatus, $raw); }
                return array('accepted' => true, 'provider' => 'akpay', 'order_no' => $gatewayTxn ?: $orderNo, 'gateway_status' => $gatewayStatus, 'message' => 'Withdrawal submitted successfully through AKPay.');
            }
        }
        $reason = trim((string)($http['error'] ?? '')) ?: 'AKPay submission could not be confirmed safely.';
        $combinedRaw = wcb_router_json(array('submit_response' => $originalRaw, 'status_response' => (string)$http['body']));
        wcb_router_mark_attempt_unknown($conn, $txId, $orderNo, 'akpay', $combinedRaw, $reason, $historyMethod);
        return array('accepted' => true, 'unknown' => true, 'provider' => 'akpay', 'order_no' => $orderNo, 'message' => 'Withdrawal is processing while AKPay confirmation is pending.');
    }
}

if (!function_exists('wcb_router_attempt_akpay')) {
    function wcb_router_attempt_akpay($conn, $txId, $uid, $amount, $wallet, $methodKey) {
        $settings = akpay_get_settings($conn);
        $merchant = trim((string)$settings['merchant_code']);
        $secret = trim((string)$settings['secret_code']);
        $accountNo = trim((string)($wallet['wallet_number'] ?? ''));
        if (in_array($methodKey, array('bkash', 'nagad', 'rocket'), true)) { $accountNo = preg_replace('/\D+/', '', $accountNo); }

        $username = 'User';
        $stmtName = $conn->prepare("SELECT username, phone FROM users WHERE id=? LIMIT 1");
        if ($stmtName) {
            $stmtName->bind_param('i', $uid);
            $stmtName->execute();
            $res = $stmtName->get_result();
            if ($res && $res->num_rows > 0) {
                $u = $res->fetch_assoc();
                $username = trim((string)($u['username'] ?? '')) ?: trim((string)($u['phone'] ?? 'User'));
            }
            $stmtName->close();
        }

        $orderNo = akpay_order_no('WA', $uid);
        $historyMethod = 'AKPay ' . akpay_method_label($methodKey) . ' (' . $accountNo . ')';
        $payload = array(
            'mchId' => $merchant,
            'out_trade_no' => $orderNo,
            'money' => akpay_format_amount($amount),
            'pay_type' => akpay_payout_method($methodKey),
            'currency' => 'BDT',
            'account' => $accountNo,
            'userName' => $username,
            'notify_url' => akpay_url('/api/akpay_payout_callback.php')
        );
        $payload['sign'] = akpay_generate_sign($payload, $secret);
        // The live AKPay server currently requires secret_key in addition to the documented MD5 sign.
        $payload['secret_key'] = $secret;
        $log = $payload;
        $log['sign'] = '***hidden***';
        $log['secret_key'] = '***hidden***';
        $rawRequest = wcb_router_json($log);
        if (!wcb_router_insert_order($conn, $txId, $orderNo, $uid, 'akpay', $methodKey, $accountNo, $amount, $rawRequest)) {
            return array('accepted' => false, 'safe_failure' => true, 'reason' => 'Unable to create AKPay payout attempt.');
        }

        $http = akpay_http_post_form(akpay_api_url($conn, '/v1/payout'), $payload, 35);
        $decoded = json_decode((string)$http['body'], true);
        $rawResponse = (string)$http['body'];
        if (!$http['success'] || !is_array($decoded)) {
            return wcb_router_akpay_reconcile_submit($conn, $txId, $orderNo, $uid, $amount, $methodKey, $accountNo, $historyMethod, $rawResponse);
        }

        $code = intval($decoded['code'] ?? -1);
        $messageText = strtolower(trim((string)($decoded['message'] ?? $decoded['msg'] ?? '')));
        if (!in_array($code, array(0, 200), true) || ($messageText !== '' && $messageText !== 'success')) {
            $reason = trim((string)($decoded['message'] ?? $decoded['msg'] ?? 'AKPay rejected the withdrawal.'));
            if ($reason === '') { $reason = 'AKPay rejected the withdrawal.'; }
            wcb_router_mark_attempt_failed($conn, $orderNo, 'akpay', 'code_' . $code, $rawResponse, $reason);
            return array('accepted' => false, 'safe_failure' => true, 'provider' => 'akpay', 'reason' => $reason);
        }

        $data = is_array($decoded['data'] ?? null) ? $decoded['data'] : array();
        $gatewayTxn = trim((string)($data['transaction_id'] ?? ''));
        $gatewayStatus = wcb_router_akpay_status_name($data['status'] ?? 'processing');
        if ($gatewayStatus === 'failed') {
            $reason = trim((string)($decoded['message'] ?? 'AKPay payout failed.'));
            wcb_router_mark_attempt_failed($conn, $orderNo, 'akpay', 'failed', $rawResponse, $reason);
            return array('accepted' => false, 'safe_failure' => true, 'provider' => 'akpay', 'reason' => $reason);
        }

        $stmt = $conn->prepare("UPDATE payment_gateway_orders SET gateway_order_no=?, gateway_status=?, raw_response=?, updated_at=NOW() WHERE order_no=? AND gateway='akpay' AND type='withdraw'");
        if ($stmt) { $stmt->bind_param('ssss', $gatewayTxn, $gatewayStatus, $rawResponse, $orderNo); $stmt->execute(); $stmt->close(); }
        wcb_router_update_selected_transaction($conn, $txId, $historyMethod, $gatewayTxn, 'akpay', $gatewayStatus);
        wcb_router_update_health($conn, 'akpay', 'up', 'Payout accepted by gateway.');
        if ($gatewayStatus === 'success') { akpay_mark_withdraw_success($conn, $orderNo, $gatewayStatus, $rawResponse); }
        return array('accepted' => true, 'provider' => 'akpay', 'order_no' => $gatewayTxn ?: $orderNo, 'gateway_status' => $gatewayStatus, 'message' => 'Withdrawal submitted successfully through AKPay.');
    }
}

if (!function_exists('wcb_router_refund_once')) {
    function wcb_router_refund_once($conn, $txId, $uid, $amount, $reason) {
        $conn->begin_transaction();
        try {
            $stmt = $conn->prepare("SELECT status FROM transactions_fake WHERE id=? AND user_id=? AND type='withdraw' LIMIT 1 FOR UPDATE");
            if (!$stmt) { throw new RuntimeException('Unable to lock withdrawal record.'); }
            $stmt->bind_param('ii', $txId, $uid);
            $stmt->execute();
            $res = $stmt->get_result();
            if (!$res || $res->num_rows === 0) { throw new RuntimeException('Withdrawal record was not found.'); }
            $status = (string)$res->fetch_assoc()['status'];
            $stmt->close();
            if (!in_array($status, array('pending', 'processing'), true)) {
                $conn->commit();
                return true;
            }
            $refund = $conn->prepare("UPDATE users SET balance=balance+? WHERE id=?");
            if (!$refund) { throw new RuntimeException('Unable to refund user balance.'); }
            $refund->bind_param('di', $amount, $uid);
            $refund->execute();
            $refund->close();
            $note = 'Automatic withdrawal failed after all gateways were checked. Balance refunded once. ' . $reason;
            $up = $conn->prepare("UPDATE transactions_fake SET status='rejected', admin_note=? WHERE id=? AND status IN ('pending','processing')");
            if (!$up) { throw new RuntimeException('Unable to reject withdrawal record.'); }
            $up->bind_param('si', $note, $txId);
            $up->execute();
            $up->close();
            $conn->commit();
            return true;
        } catch (Throwable $e) {
            $conn->rollback();
            error_log('Withdrawal final refund failed: ' . $e->getMessage());
            return false;
        }
    }
}

if (!function_exists('wcb_route_withdrawal')) {
    function wcb_route_withdrawal($conn, $uid, $amount, $wallet) {
        $uid = intval($uid);
        $amount = round((float)$amount, 2);
        $methodKey = strtolower(trim((string)($wallet['method_code'] ?? ($wallet['method'] ?? ''))));
        if ($methodKey === 'b-kash') { $methodKey = 'bkash'; }
        $accountNo = trim((string)($wallet['wallet_number'] ?? ''));
        if (in_array($methodKey, array('bkash', 'nagad', 'rocket'), true)) {
            $digits = preg_replace('/\D+/', '', $accountNo);
            if (!preg_match('/^01\d{9}$/', $digits)) { return array('success' => false, 'message' => 'Enter a valid 11 digit wallet number.'); }
        }

        $gateways = wcb_router_gateway_rows($conn, $methodKey);
        if (empty($gateways)) {
            return array('success' => false, 'message' => 'No enabled withdrawal gateway supports the selected method.');
        }

        $eligible = array();
        $reasons = array();
        foreach ($gateways as $gateway) {
            $provider = strtolower((string)$gateway['provider']);
            $preflight = wcb_router_preflight($conn, $gateway, $amount);
            if (!empty($preflight['eligible'])) {
                $eligible[] = $gateway;
            } else {
                $reason = trim((string)($preflight['message'] ?? 'Preflight check failed.'));
                $reasons[] = strtoupper($provider) . ': ' . $reason;
                wcb_router_update_health($conn, $provider, wcb_router_reason_is_insufficient($reason) ? 'up' : 'down', $reason);
            }
        }
        if (empty($eligible)) {
            $message = 'No payment gateway currently has enough available payout balance.';
            if (!empty($reasons)) { $message .= ' ' . implode(' | ', $reasons); }
            return array('success' => false, 'message' => $message);
        }

        $reserved = wcb_router_reserve_user_balance($conn, $uid, $amount, $wallet, $methodKey);
        if (empty($reserved['success'])) { return $reserved; }
        $txId = intval($reserved['transaction_id']);

        foreach ($eligible as $gateway) {
            $provider = strtolower((string)$gateway['provider']);
            if ($provider === 'propay') {
                $attempt = wcb_router_attempt_propay($conn, $txId, $uid, $amount, $wallet, $methodKey);
            } elseif ($provider === 'akpay') {
                $attempt = wcb_router_attempt_akpay($conn, $txId, $uid, $amount, $wallet, $methodKey);
            } else {
                $attempt = array('accepted' => false, 'safe_failure' => true, 'reason' => 'Unsupported gateway.');
            }
            if (!empty($attempt['accepted'])) {
                return array(
                    'success' => true,
                    'message' => (string)($attempt['message'] ?? 'Withdrawal submitted successfully.'),
                    'gateway' => $provider,
                    'order_no' => (string)($attempt['order_no'] ?? ''),
                    'gateway_status' => (string)($attempt['gateway_status'] ?? (!empty($attempt['unknown']) ? 'submit_unknown' : 'processing'))
                );
            }
            $reason = trim((string)($attempt['reason'] ?? 'Gateway rejected the withdrawal.'));
            $reasons[] = strtoupper($provider) . ': ' . $reason;
            // Only definitive failures reach the next gateway. Ambiguous outcomes return accepted=true above.
        }

        $reasonText = !empty($reasons) ? implode(' | ', $reasons) : 'All payout gateways rejected the request.';
        wcb_router_refund_once($conn, $txId, $uid, $amount, $reasonText);
        $insufficient = false;
        foreach ($reasons as $reason) { if (wcb_router_reason_is_insufficient($reason)) { $insufficient = true; break; } }
        return array(
            'success' => false,
            'message' => $insufficient
                ? 'No merchant account currently has enough payout balance. Your website balance was restored.'
                : 'All withdrawal gateways are currently unavailable. Your website balance was restored.'
        );
    }
}
