<?php
/**
 * Binance P2P API Integration
 * Handles all Binance P2P API calls
 */

class BinanceP2PAPI {
    private $apiKey;
    private $secretKey;
    private $baseUrl;
    
    public function __construct($apiKey, $secretKey, $baseUrl = 'https://api.binance.com') {
        $this->apiKey = $apiKey;
        $this->secretKey = $secretKey;
        $this->baseUrl = $baseUrl;
    }
    
    /**
     * Generate HMAC SHA256 signature
     */
    private function generateSignature($queryString) {
        return hash_hmac('sha256', $queryString, $this->secretKey);
    }
    
    /**
     * Get timestamp in milliseconds
     */
    private function getTimestamp() {
        return round(microtime(true) * 1000);
    }
    
    /**
     * Make a signed request to Binance API
     */
    private function signedRequest($method, $endpoint, $params = []) {
        $timestamp = $this->getTimestamp();
        
        // Special handling for POST getUserOrderDetail - orderNo goes in body as adOrderNo
        $isGetUserOrderDetail = ($endpoint === '/sapi/v1/c2c/orderMatch/getUserOrderDetail' && $method === 'POST');
        
        // Special handling for POST markOrderAsPaid endpoint
        $isMarkOrderAsPaidEndpoint = ($endpoint === '/sapi/v1/c2c/orderMatch/markOrderAsPaid' && $method === 'POST');
        
        // Special handling for POST cancelOrder endpoint - parameters go in POST body
        $isCancelOrder = ($endpoint === '/sapi/v1/c2c/orderMatch/cancelOrder' && $method === 'POST');
        
        // Special handling for POST merchantOrder (release asset) - parameters go in POST body
        $isMerchantOrder = ($endpoint === '/sapi/v1/c2c/orderMatch/merchantOrder' && $method === 'POST');
        
        // Special handling for Convert API POST endpoints - parameters go in query string, not body
        $isConvertPost = (strpos($endpoint, '/sapi/v1/convert/') !== false && $method === 'POST');
        
        // Special handling for get-funding-asset endpoint - parameters go in query string
        $isGetFundingAsset = ($endpoint === '/sapi/v1/asset/get-funding-asset' && $method === 'POST');
        
        // Special handling for asset transfer endpoint - parameters go in POST body
        $isAssetTransfer = ($endpoint === '/sapi/v1/asset/transfer' && $method === 'POST');
        
        // Special handling for pre-signed URL endpoint - empty body, only timestamp in query
        $isPreSignedUrl = ($endpoint === '/sapi/v1/c2c/chat/image/pre-signed-url' && $method === 'POST');
        
        if ($isGetUserOrderDetail) {
            // For this endpoint: only timestamp and signature in query string
            // adOrderNo goes in POST body
            $queryParams = ['timestamp' => $timestamp];
            $queryString = http_build_query($queryParams);
            $signature = $this->generateSignature($queryString);
            $url = $this->baseUrl . $endpoint . '?' . $queryString . '&signature=' . $signature;
            
            // Extract orderNo from params and use as adOrderNo in body
            $adOrderNo = $params['orderNo'] ?? $params['adOrderNo'] ?? null;
            $bodyData = json_encode(['adOrderNo' => $adOrderNo]);
        } elseif ($isMarkOrderAsPaidEndpoint) {
            // For markOrderAsPaid endpoint: only timestamp and signature in query string
            // orderNumber and payId go in POST body
            $queryParams = ['timestamp' => $timestamp];
            $queryString = http_build_query($queryParams);
            $signature = $this->generateSignature($queryString);
            $url = $this->baseUrl . $endpoint . '?' . $queryString . '&signature=' . $signature;
            
            // Extract orderNumber and payId from params for body
            $bodyParams = [
                'orderNumber' => $params['orderNumber'] ?? $params['orderNo'] ?? null,
                'payId' => $params['payId'] ?? 0
            ];
            $bodyData = json_encode($bodyParams);
        } elseif ($isCancelOrder) {
            // For cancelOrder endpoint: only timestamp and signature in query string
            // orderNumber, orderCancelReasonCode, and orderCancelAdditionalInfo go in POST body
            $queryParams = ['timestamp' => $timestamp];
            $queryString = http_build_query($queryParams);
            $signature = $this->generateSignature($queryString);
            $url = $this->baseUrl . $endpoint . '?' . $queryString . '&signature=' . $signature;
            
            // Extract parameters for body
            $bodyParams = [
                'orderNumber' => $params['orderNumber'] ?? $params['orderNo'] ?? null,
                'orderCancelReasonCode' => $params['orderCancelReasonCode'] ?? $params['cancelReasonCode'] ?? 4
            ];
            
            // Add additional info if provided
            if (isset($params['orderCancelAdditionalInfo']) && !empty($params['orderCancelAdditionalInfo'])) {
                $bodyParams['orderCancelAdditionalInfo'] = $params['orderCancelAdditionalInfo'];
            }
            
            $bodyData = json_encode($bodyParams);
        } elseif ($isMerchantOrder) {
            // For merchantOrder endpoint (release asset): only timestamp and signature in query string
            // orderNo and operateType go in POST body
            $queryParams = ['timestamp' => $timestamp];
            $queryString = http_build_query($queryParams);
            $signature = $this->generateSignature($queryString);
            $url = $this->baseUrl . $endpoint . '?' . $queryString . '&signature=' . $signature;
            
            // Extract orderNo and operateType from params for body
            $bodyParams = [];
            if (isset($params['orderNo'])) {
                $bodyParams['orderNo'] = $params['orderNo'];
            }
            if (isset($params['operateType'])) {
                $bodyParams['operateType'] = $params['operateType'];
            }
            $bodyData = json_encode($bodyParams);
        } elseif ($isAssetTransfer) {
            // For asset transfer endpoint: try query string first (most Binance endpoints use this)
            // If that doesn't work, we can try POST body
            $params['timestamp'] = $timestamp;
            $queryString = http_build_query($params);
            $signature = $this->generateSignature($queryString);
            $url = $this->baseUrl . $endpoint . '?' . $queryString . '&signature=' . $signature;
            $bodyData = null;
        } elseif ($isPreSignedUrl) {
            // Pre-signed URL endpoint: timestamp and signature in query string, imageName in POST body
            // Error -1102 indicates imageName is a mandatory parameter
            $queryParams = ['timestamp' => $timestamp];
            $queryString = http_build_query($queryParams);
            $signature = $this->generateSignature($queryString);
            $url = $this->baseUrl . $endpoint . '?' . $queryString . '&signature=' . $signature;
            // imageName must be in POST body as JSON
            $imageName = $params['imageName'] ?? 'image_' . time() . '.jpg';
            $bodyData = json_encode(['imageName' => $imageName]);
        } elseif ($isConvertPost || $isGetFundingAsset) {
            // Convert API POST endpoints and get-funding-asset: params in query string (not body)
            $params['timestamp'] = $timestamp;
            $queryString = http_build_query($params);
            $signature = $this->generateSignature($queryString);
            $url = $this->baseUrl . $endpoint . '?' . $queryString . '&signature=' . $signature;
            $bodyData = null;
        } else {
            // Standard request - all params in query string
            $params['timestamp'] = $timestamp;
            $queryString = http_build_query($params);
            $signature = $this->generateSignature($queryString);
            $url = $this->baseUrl . $endpoint . '?' . $queryString . '&signature=' . $signature;
            $bodyData = null;
        }
        
        $ch = curl_init($url);
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
        
        // Build headers
        $headers = [
            'X-MBX-APIKEY: ' . $this->apiKey,
            'clientType: WEB'
        ];
        // Always include Content-Type for POST requests (even if body is empty JSON)
        if ($method === 'POST' && $bodyData !== null) {
            $headers[] = 'Content-Type: application/json';
        }
        curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
        
        if ($method === 'POST') {
            curl_setopt($ch, CURLOPT_POST, true);
            if ($bodyData !== null) {
                curl_setopt($ch, CURLOPT_POSTFIELDS, $bodyData);
            }
        } elseif ($method === 'PUT') {
            curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'PUT');
            if ($bodyData !== null) {
                curl_setopt($ch, CURLOPT_POSTFIELDS, $bodyData);
            }
        } elseif ($method === 'DELETE') {
            curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'DELETE');
        }
        
        $response = curl_exec($ch);
        $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
        $curlError = curl_error($ch);
        curl_close($ch);
        
        if ($curlError) {
            error_log("Binance API cURL Error ($method $endpoint): " . $curlError);
            return ['error' => 'cURL Error: ' . $curlError];
        }
        
        $responseData = json_decode($response, true);
        
        // Log raw response for debugging (especially for POST getUserOrderDetail and markOrderAsPaid)
        $shouldLogDetailed = $isGetUserOrderDetail || $isMarkOrderAsPaidEndpoint || $isMerchantOrder || $isPreSignedUrl || $isCancelOrder;
        
        if ($shouldLogDetailed) {
            error_log("Binance API $method $endpoint - Request URL: $url");
            error_log("Binance API $method $endpoint - Headers: " . json_encode($headers));
            if ($bodyData !== null) {
                error_log("Binance API $method $endpoint - Request body: " . ($bodyData ?: 'EMPTY'));
            } else {
                error_log("Binance API $method $endpoint - Request body: NULL (no body)");
            }
            error_log("Binance API $method $endpoint - Raw response: " . ($response ? substr($response, 0, 2000) : 'EMPTY'));
            error_log("Binance API $method $endpoint - HTTP Code: $httpCode");
            error_log("Binance API $method $endpoint - Decoded response: " . json_encode($responseData));
        }
        
        // Special handling for markOrderAsPaid and cancelOrder endpoints: empty response with HTTP 200 means success
        if (($isMarkOrderAsPaidEndpoint || $isCancelOrder) && $httpCode === 200 && (empty($response) || $responseData === null)) {
            $action = $isCancelOrder ? 'cancelled' : 'marked as paid';
            error_log("Binance API $method $endpoint - Empty response with HTTP 200, treating as success (order $action)");
            return ['code' => '000000', 'success' => true, 'message' => $isCancelOrder ? 'Order cancelled successfully' : 'Order marked as paid successfully'];
        }
        
        // Check for empty or null response (for other endpoints)
        if (empty($response) || $responseData === null) {
            $errorMessage = "Empty or invalid response from Binance API";
            error_log("Binance API Error ($method $endpoint): HTTP $httpCode - $errorMessage");
            return ['error' => $errorMessage, 'http_code' => $httpCode, 'raw_response' => $response];
        }
        
        if ($httpCode >= 200 && $httpCode < 300) {
            // Check for Binance API error indicators in successful HTTP responses
            // Binance often returns HTTP 200 but with error in response body
            if (isset($responseData['code']) && $responseData['code'] !== '000000' && $responseData['code'] !== 0) {
                $errorMessage = isset($responseData['msg']) ? $responseData['msg'] : (isset($responseData['message']) ? $responseData['message'] : 'API returned error code');
                error_log("Binance API Error ($method $endpoint): HTTP $httpCode but error code in response - " . $errorMessage);
                return ['error' => $errorMessage, 'http_code' => $httpCode, 'code' => $responseData['code'], 'response' => $responseData];
            }
            
            return $responseData;
        } else {
            $errorMessage = isset($responseData['msg']) ? $responseData['msg'] : (isset($responseData['message']) ? $responseData['message'] : 'Unknown error');
            error_log("Binance API Error ($method $endpoint): HTTP $httpCode - " . $errorMessage);
            
            // Include full response in error for debugging
            if ($httpCode >= 500) {
                error_log("Binance API Error - Full response: " . json_encode($responseData, JSON_PRETTY_PRINT));
            }
            
            return ['error' => $errorMessage, 'http_code' => $httpCode, 'code' => $responseData['code'] ?? null, 'response' => $responseData];
        }
    }
    
    /**
     * Get user order detail
     * According to Binance Support: Use POST /sapi/v1/c2c/orderMatch/getUserOrderDetail
     * Payment details should be in paymentMethodFields array
     */
    public function getUserOrderDetail($orderNo) {
        // Binance Support confirmed: Use POST method
        error_log("getUserOrderDetail - Using POST method as confirmed by Binance Support");
        $detailResult = $this->signedRequest('POST', '/sapi/v1/c2c/orderMatch/getUserOrderDetail', [
            'orderNo' => $orderNo
        ]);
        
        // Check if successful
        if (!isset($detailResult['error']) && isset($detailResult['data'])) {
            $orderData = $detailResult['data'];
            error_log("getUserOrderDetail - Successfully retrieved order detail via POST");
            
            // Log payment-related fields found
            if (isset($orderData['paymentMethodFields'])) {
                error_log("getUserOrderDetail - Found paymentMethodFields with " . count($orderData['paymentMethodFields']) . " fields");
            }
            if (isset($orderData['payAccount'])) {
                error_log("getUserOrderDetail - Found payAccount field");
            }
            if (isset($orderData['buyerPaymentInfo'])) {
                error_log("getUserOrderDetail - Found buyerPaymentInfo");
            }
            
            return $detailResult;
        } else {
            // Log the error for debugging
            $errorMsg = $detailResult['error'] ?? 'Unknown error';
            $httpCode = $detailResult['http_code'] ?? 'N/A';
            error_log("getUserOrderDetail - POST request failed: HTTP $httpCode - $errorMsg");
            
            // Log full response for debugging
            error_log("getUserOrderDetail - Full error response: " . json_encode($detailResult, JSON_PRETTY_PRINT));
        }
        
        // Final fallback: use listUserOrderHistory
        error_log("getUserOrderDetail - getUserOrderDetail endpoint failed or missing payment details, using listUserOrderHistory fallback");
        $listResult = $this->signedRequest('GET', '/sapi/v1/c2c/orderMatch/listUserOrderHistory', [
            'orderNo' => $orderNo,
            'page' => 1,
            'rows' => 50  // Get more orders to ensure we find the one we need
        ]);
        
        if (isset($listResult['error'])) {
            error_log("getUserOrderDetail - listUserOrderHistory error: " . $listResult['error']);
            return $listResult;
        }
        
        // Extract the matching order from the response
        $orders = $listResult['data'] ?? [];
        
        // Log the FULL response structure (first 5000 chars) to help debug
        error_log("getUserOrderDetail - Full listUserOrderHistory response (first 5000 chars): " . substr(json_encode($listResult, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE), 0, 5000));
        
        // Log full response structure for debugging payment details
        error_log("getUserOrderDetail - Response keys: " . implode(', ', array_keys($listResult)));
        if (is_array($orders) && !empty($orders) && isset($orders[0])) {
            $firstOrderKeys = array_keys($orders[0]);
            error_log("getUserOrderDetail - First order keys: " . implode(', ', $firstOrderKeys));
            
            // Check for payment-related keys
            $paymentKeys = array_filter($firstOrderKeys, function($key) {
                return stripos($key, 'payment') !== false || 
                       stripos($key, 'iban') !== false || 
                       stripos($key, 'bank') !== false ||
                       stripos($key, 'field') !== false ||
                       stripos($key, 'method') !== false;
            });
            if (!empty($paymentKeys)) {
                error_log("getUserOrderDetail - Payment-related keys found: " . implode(', ', $paymentKeys));
            } else {
                error_log("getUserOrderDetail - WARNING: No payment-related keys found in order response");
            }
            
            // Deep log: Check for paymentMethodFields specifically (per SAPI v7.4)
            $order = $orders[0];
            if (isset($order['paymentMethodFields'])) {
                error_log("getUserOrderDetail - Found paymentMethodFields array!");
                error_log("getUserOrderDetail - paymentMethodFields structure: " . json_encode($order['paymentMethodFields']));
            } else {
                // Recursively search for paymentMethodFields
                $searchForFields = function($data, $path = '') use (&$searchForFields) {
                    if (is_array($data)) {
                        foreach ($data as $key => $value) {
                            $currentPath = $path ? "$path.$key" : $key;
                            if (strtolower($key) === 'paymentmethodfields' || strtolower($key) === 'payment_method_fields') {
                                error_log("getUserOrderDetail - Found paymentMethodFields at path: $currentPath");
                                error_log("getUserOrderDetail - paymentMethodFields value: " . json_encode($value));
                            }
                            if (is_array($value)) {
                                $searchForFields($value, $currentPath);
                            }
                        }
                    }
                };
                $searchForFields($order);
            }
        }
        
        // If orders is an array, find the matching one
        if (is_array($orders)) {
            // Check if it's a list (numeric keys) or single order
            foreach ($orders as $key => $order) {
                if (is_numeric($key)) {
                    // It's a list, find matching order
                    $orderNumber = $order['orderNumber'] ?? $order['orderNo'] ?? null;
                    if ($orderNumber === $orderNo) {
                        error_log("getUserOrderDetail - Found matching order at index $key");
                        return ['data' => $order];
                    }
                } else {
                    // It's a single order object, check if it matches
                    $orderNumber = $order['orderNumber'] ?? $order['orderNo'] ?? null;
                    if ($orderNumber === $orderNo) {
                        error_log("getUserOrderDetail - Found matching order at root level");
                        return ['data' => $order];
                    }
                }
            }
            
            // If not found but list exists, return first order (should be the requested one)
            if (!empty($orders) && isset($orders[0])) {
                error_log("getUserOrderDetail - Using first order from list");
                $order = $orders[0];
                
                // Log the FULL order structure to help debug payment details location
                error_log("getUserOrderDetail - Full order structure (first 2000 chars): " . substr(json_encode($order, JSON_PRETTY_PRINT), 0, 2000));
                
                return ['data' => $order];
            }
        }
        
        // Log the full response if we couldn't find the order
        error_log("getUserOrderDetail - Full response structure: " . substr(json_encode($listResult, JSON_PRETTY_PRINT), 0, 2000));
        
        return $listResult;
    }
    
    /**
     * Retrieve user order list with pagination
     */
    public function getUserOrderList($page = 1, $rows = 20, $startTimestamp = null, $endTimestamp = null) {
        $params = [
            'page' => $page,
            'rows' => $rows
        ];
        
        if ($startTimestamp) {
            $params['startTimestamp'] = $startTimestamp;
        }
        if ($endTimestamp) {
            $params['endTimestamp'] = $endTimestamp;
        }
        
        return $this->signedRequest('GET', '/sapi/v1/c2c/orderMatch/listUserOrderHistory', $params);
    }
    
    /**
     * Mark order as paid
     * Uses POST /sapi/v1/c2c/orderMatch/markOrderAsPaid endpoint
     * Body: {"orderNumber": "string", "payId": 0}
     */
    public function markOrderAsPaid($orderNo, $payId = 0) {
        return $this->signedRequest('POST', '/sapi/v1/c2c/orderMatch/markOrderAsPaid', [
            'orderNumber' => $orderNo,
            'payId' => $payId
        ]);
    }
    
    /**
     * Release digital asset (crypto) to buyer
     */
    public function releaseDigitalAsset($orderNo) {
        return $this->signedRequest('POST', '/sapi/v1/c2c/orderMatch/merchantOrder', [
            'orderNo' => $orderNo,
            'operateType' => 'RELEASE'
        ]);
    }
    
    /**
     * Cancel order on Binance P2P
     * @param string $orderNo Order number
     * @param int $cancelReasonCode Cancel reason code (1-10). Code 4 = "Payment information does not match"
     */
    /**
     * Cancel a P2P order
     * Uses POST /sapi/v1/c2c/orderMatch/cancelOrder endpoint
     * Body: {"orderNumber": "string", "orderCancelReasonCode": 4, "orderCancelAdditionalInfo": "string"}
     * 
     * Reason codes:
     * 1: I change mind
     * 2: I don't meet seller's requirement
     * 3: Seller asking extra fee
     * 4: Seller's payment method issue (default)
     * 5: Other
     * 6: Seller cannot release
     */
    public function cancelOrder($orderNo, $cancelReasonCode = 4, $additionalInfo = '') {
        $params = [
            'orderNumber' => $orderNo,
            'orderCancelReasonCode' => (int)$cancelReasonCode
        ];
        
        // Add additional info if provided
        if (!empty($additionalInfo)) {
            $params['orderCancelAdditionalInfo'] = $additionalInfo;
        }
        
        return $this->signedRequest('POST', '/sapi/v1/c2c/orderMatch/cancelOrder', $params);
    }
    
    /**
     * Get pre-signed URL for image upload
     * Returns preSignedUrl and imageUrl
     * Limited to 36 times per minute per user id
     * @param string $imageName The name of the image file (e.g., "proof_123.jpg")
     * @return array With keys: preSignedUrl, imageUrl (or error if failed)
     */
    public function getImagePreSignedUrl($imageName = null) {
        // Generate default image name if not provided
        if (empty($imageName)) {
            $imageName = 'proof_' . time() . '.jpg';
        }
        
        // imageName should be in POST body according to error message
        $result = $this->signedRequest('POST', '/sapi/v1/c2c/chat/image/pre-signed-url', [
            'imageName' => $imageName
        ]);
        
        // Check for errors
        if (isset($result['error'])) {
            return $result;
        }
        
        // Binance returns: { "code": "000000", "message": "success", "data": { "uploadUrl": "...", "imageUrl": "..." }, "success": true }
        if (isset($result['data']['uploadUrl']) && isset($result['data']['imageUrl'])) {
            return [
                'preSignedUrl' => $result['data']['uploadUrl'],
                'imageUrl' => $result['data']['imageUrl']
            ];
        }
        
        // Fallback: check if keys are at top level (for compatibility)
        if (isset($result['uploadUrl']) && isset($result['imageUrl'])) {
            return [
                'preSignedUrl' => $result['uploadUrl'],
                'imageUrl' => $result['imageUrl']
            ];
        }
        
        // If we get here, the response format is unexpected
        return [
            'error' => 'Unexpected response format from Binance API',
            'response' => $result
        ];
    }
    
    /**
     * Upload image to pre-signed URL
     * @param string $preSignedUrl The pre-signed URL from getImagePreSignedUrl()
     * @param string $imagePath Full path to the image file
     * @return array Result with success status
     */
    public function uploadImageToPreSignedUrl($preSignedUrl, $imagePath) {
        if (!file_exists($imagePath)) {
            return ['success' => false, 'error' => 'Image file not found: ' . $imagePath];
        }
        
        // Open file for reading (binary mode)
        $fp = fopen($imagePath, 'rb');
        if ($fp === false) {
            return ['success' => false, 'error' => 'Failed to open image file'];
        }
        
        // Get file size
        $fileSize = filesize($imagePath);
        
        $ch = curl_init($preSignedUrl);
        curl_setopt($ch, CURLOPT_PUT, true);
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
        curl_setopt($ch, CURLOPT_INFILE, $fp);
        curl_setopt($ch, CURLOPT_INFILESIZE, $fileSize);
        // Don't set Content-Type - let S3 auto-detect or use what's in the pre-signed URL
        
        $response = curl_exec($ch);
        $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
        $curlError = curl_error($ch);
        
        fclose($fp);
        curl_close($ch);
        
        if ($curlError) {
            error_log("Error uploading image to pre-signed URL: " . $curlError);
            return ['success' => false, 'error' => 'cURL Error: ' . $curlError];
        }
        
        if ($httpCode !== 200) {
            error_log("Failed to upload image. HTTP Code: $httpCode, Response: " . substr($response, 0, 500));
            return ['success' => false, 'error' => "Upload failed with HTTP $httpCode", 'response' => $response];
        }
        
        return ['success' => true];
    }
    
    /**
     * Get Payment Method by UserId (per SAPI v7.4 item 32)
     * This might contain payment details for a specific user
     */
    public function getPaymentMethodByUserId($userId) {
        return $this->signedRequest('GET', '/sapi/v1/c2c/payment/getPaymentMethodByUserId', [
            'userId' => $userId
        ]);
    }
    
    /**
     * Get chat credentials for WebSocket connection
     */
    public function getChatCredentials() {
        return $this->signedRequest('GET', '/sapi/v1/c2c/chat/retrieveChatCredential');
    }
    
    /**
     * Retrieve chat messages with pagination
     */
    public function getChatMessages($orderNo, $page = 1, $rows = 20) {
        return $this->signedRequest('GET', '/sapi/v1/c2c/chat/retrieveChatMessagesWithPagination', [
            'orderNo' => $orderNo,
            'page' => $page,
            'rows' => $rows
        ]);
    }
    
    /**
     * Get pending orders (orders waiting for payment)
     * For merchants buying crypto: We need orders where buyer is waiting for merchant to pay
     */
    public function getPendingOrders($page = 1, $rows = 100) {
        $orders = $this->getUserOrderList($page, $rows);
        
        if (isset($orders['error'])) {
            return $orders;
        }
        
        // Log all orders received for debugging
        $allOrders = $orders['data'] ?? [];
        error_log("Binance API returned " . count($allOrders) . " total orders");
        
        // Filter orders with status that need payment
        // Binance P2P statuses: 
        // - TRADING = Order is active, waiting for merchant to pay (this is what we need!)
        // - PENDING = Order pending
        // - WAITING_PAYMENT = Waiting for payment
        // - APPEAL = Order in appeal
        // - BUYER_PAYED = Merchant already marked order as paid (SKIP - already processed)
        // - COMPLETED = Order completed (SKIP)
        // - CANCELLED = Order cancelled (SKIP)
        $pendingStatuses = ['TRADING', 'PENDING', 'WAITING_PAYMENT', 'APPEAL'];
        $skipStatuses = ['BUYER_PAYED', 'COMPLETED', 'CANCELLED', 'RELEASED'];
        $pendingOrders = [];
        
        if (is_array($allOrders)) {
            foreach ($allOrders as $order) {
                $status = $order['orderStatus'] ?? $order['status'] ?? '';
                $orderNo = $order['orderNumber'] ?? $order['orderNo'] ?? $order['order_number'] ?? 'UNKNOWN';
                $tradeType = $order['tradeType'] ?? $order['trade_type'] ?? '';
                
                // Skip if already marked as paid or completed
                if (in_array(strtoupper($status), $skipStatuses)) {
                    if (strtoupper($tradeType) === 'BUY') {
                        error_log("Order $orderNo: Skipped - already processed (status='$status')");
                    }
                    continue;
                }
                
                // For merchant buying crypto: We need BUY orders with TRADING status
                // TRADING means order is active and waiting for merchant to pay
                // BUY means we're buying crypto (merchant side)
                if (strtoupper($tradeType) === 'BUY' && in_array(strtoupper($status), $pendingStatuses)) {
                    $pendingOrders[] = $order;
                    error_log("Order $orderNo: Added (status='$status', tradeType='$tradeType')");
                } else {
                    // Only log skipped orders if they're BUY type (to reduce log spam)
                    if (strtoupper($tradeType) === 'BUY') {
                        error_log("Order $orderNo: Skipped (status='$status', tradeType='$tradeType')");
                    }
                }
            }
        }
        
        error_log("Filtered to " . count($pendingOrders) . " pending orders");
        
        return [
            'success' => true,
            'data' => $pendingOrders,
            'total' => count($pendingOrders)
        ];
    }
    
    /**
     * Prepare chat message structure (actual sending done via WebSocket)
     */
    public function prepareChatMessage($orderNo, $content, $messageType = 'text') {
        return [
            'type' => $messageType,
            'uuid' => (string)(time() * 1000) . rand(1000, 9999),
            'orderNo' => $orderNo,
            'content' => $content,
            'self' => true,
            'clientType' => 'web',
            'createTime' => time() * 1000,
            'sendStatus' => 0
        ];
    }
    
    /**
     * Get account balance for a specific asset
     * ONLY checks funding and spot accounts (NOT margin)
     * @param string $asset Asset symbol (e.g., 'USDT', 'USDC')
     * @return array Response with balance information
     */
    public function getAccountBalance($asset) {
        $assetUpper = strtoupper($asset);
        
        // 1. Try funding account FIRST (where deposits usually go) - PRIORITY
        error_log("Checking funding account for $assetUpper...");
        $fundingResponse = $this->getFundingBalance($asset);
        
        $fundingFree = floatval($fundingResponse['free'] ?? 0);
        $fundingTotal = floatval($fundingResponse['total'] ?? 0);
        
        // 2. ALWAYS check spot account too (in case asset is there)
        error_log("Checking spot account for $assetUpper...");
        $spotResponse = $this->signedRequest('GET', '/api/v3/account');
        $spotFree = 0;
        $spotLocked = 0;
        $spotTotal = 0;
        
        if (!isset($spotResponse['error'])) {
            $balances = $spotResponse['balances'] ?? [];
            foreach ($balances as $balance) {
                if (strtoupper($balance['asset'] ?? '') === $assetUpper) {
                    $spotFree = floatval($balance['free'] ?? 0);
                    $spotLocked = floatval($balance['locked'] ?? 0);
                    $spotTotal = $spotFree + $spotLocked;
                    if ($spotTotal > 0) {
                        error_log("✓ Found $assetUpper in spot account: Free=$spotFree, Locked=$spotLocked, Total=$spotTotal");
                    }
                    break;
                }
            }
        }
        
        // 3. Combine balances from both accounts and return total
        $combinedFree = $fundingFree + $spotFree;
        $combinedTotal = $fundingTotal + $spotTotal;
        
        if ($combinedTotal > 0) {
            // Determine which account(s) have the balance
            $accountTypes = [];
            if ($fundingTotal > 0) {
                $accountTypes[] = 'funding';
                error_log("  - Funding account: $fundingTotal $assetUpper");
            }
            if ($spotTotal > 0) {
                $accountTypes[] = 'spot';
                error_log("  - Spot account: $spotTotal $assetUpper");
            }
            $accountType = implode('+', $accountTypes);
            
            error_log("✓ Found $assetUpper in both accounts: Total=$combinedTotal (Funding: $fundingTotal, Spot: $spotTotal)");
            
            return [
                'success' => true,
                'asset' => $fundingResponse['asset'] ?? $assetUpper,
                'free' => $combinedFree,
                'locked' => floatval($fundingResponse['locked'] ?? 0) + $spotLocked,
                'total' => $combinedTotal,
                'account_type' => $accountType,
                'funding_balance' => $fundingTotal,
                'spot_balance' => $spotTotal
            ];
        }
        
        error_log("✗ $assetUpper not found in funding or spot account (Funding: $fundingTotal, Spot: $spotTotal)");
        return [
            'success' => true,
            'asset' => $asset,
            'free' => 0,
            'locked' => 0,
            'total' => 0,
            'account_type' => 'none'
        ];
    }
    
    /**
     * Get funding account balance (for deposits/withdrawals)
     * @param string $asset Asset symbol
     * @return array Response with balance information
     */
    public function getFundingBalance($asset) {
        $assetUpper = strtoupper($asset);
        
        error_log("=== SEARCHING FOR $assetUpper IN FUNDING ACCOUNT ===");
        
        // Try multiple methods to find USDT
        
        // Method 0: Try the dedicated funding asset endpoint (POST /sapi/v1/asset/get-funding-asset)
        error_log("Method 0: Using dedicated funding asset endpoint for $assetUpper...");
        $fundingAssetResponse = $this->signedRequest('POST', '/sapi/v1/asset/get-funding-asset', [
            'asset' => $assetUpper
        ]);
        
        if (!isset($fundingAssetResponse['error']) && is_array($fundingAssetResponse)) {
            error_log("Funding asset endpoint returned " . count($fundingAssetResponse) . " result(s)");
            
            // The response is an array of assets
            foreach ($fundingAssetResponse as $balance) {
                if (is_array($balance)) {
                    $balanceAsset = strtoupper($balance['asset'] ?? $balance['coin'] ?? 'unknown');
                    $free = floatval($balance['free'] ?? $balance['amount'] ?? 0);
                    $locked = floatval($balance['locked'] ?? 0);
                    $freeze = floatval($balance['freeze'] ?? 0);
                    $withdrawing = floatval($balance['withdrawing'] ?? 0);
                    $total = $free + $locked + $freeze + $withdrawing;
                    
                    error_log("Funding asset result: $balanceAsset - Free: $free, Locked: $locked, Freeze: $freeze, Withdrawing: $withdrawing");
                    
                    if ($balanceAsset === $assetUpper) {
                        error_log("✓✓✓ FOUND $assetUpper in funding asset endpoint!");
                        error_log("  Free: $free, Locked: $locked, Freeze: $freeze, Withdrawing: $withdrawing");
                        error_log("  Total (all states): $total");
                        error_log("  Full data: " . json_encode($balance));
                        
                        return [
                            'success' => true,
                            'asset' => $balance['asset'] ?? $balance['coin'] ?? $asset,
                            'free' => $free,
                            'locked' => $locked,
                            'total' => $total,
                            'account_type' => 'funding'
                        ];
                    }
                }
            }
        } else {
            error_log("Funding asset endpoint error or empty: " . json_encode($fundingAssetResponse));
            
            // Try querying all funding assets (without asset parameter)
            error_log("Method 0b: Trying to get ALL funding assets (no asset filter)...");
            $allFundingAssetsResponse = $this->signedRequest('POST', '/sapi/v1/asset/get-funding-asset', []);
            
            if (!isset($allFundingAssetsResponse['error']) && is_array($allFundingAssetsResponse)) {
                error_log("All funding assets returned " . count($allFundingAssetsResponse) . " result(s)");
                
                foreach ($allFundingAssetsResponse as $balance) {
                    if (is_array($balance)) {
                        $balanceAsset = strtoupper($balance['asset'] ?? $balance['coin'] ?? 'unknown');
                        $free = floatval($balance['free'] ?? $balance['amount'] ?? 0);
                        $locked = floatval($balance['locked'] ?? 0);
                        $freeze = floatval($balance['freeze'] ?? 0);
                        $withdrawing = floatval($balance['withdrawing'] ?? 0);
                        $total = $free + $locked + $freeze + $withdrawing;
                        
                        error_log("All funding assets - $balanceAsset: Free=$free, Locked=$locked, Total=$total");
                        
                        if ($balanceAsset === $assetUpper && $total > 0) {
                            error_log("✓✓✓ FOUND $assetUpper in all funding assets!");
                            return [
                                'success' => true,
                                'asset' => $balance['asset'] ?? $balance['coin'] ?? $asset,
                                'free' => $free,
                                'locked' => $locked,
                                'total' => $total,
                                'account_type' => 'funding'
                            ];
                        }
                    }
                }
            }
        }
        
        // Method 1: Try direct query for specific asset using getUserAsset
        error_log("Method 1: DIRECT QUERY for asset $assetUpper using getUserAsset...");
        $directBalanceResponse = $this->signedRequest('GET', '/sapi/v1/asset/getUserAsset', [
            'asset' => $assetUpper
        ]);
        
        if (!isset($directBalanceResponse['error']) && is_array($directBalanceResponse)) {
            error_log("Direct query returned " . count($directBalanceResponse) . " result(s)");
            
            // Log ALL results from direct query to see what we got
            foreach ($directBalanceResponse as $idx => $balance) {
                if (is_array($balance)) {
                    $balanceAsset = strtoupper($balance['asset'] ?? $balance['coin'] ?? 'unknown');
                    $free = floatval($balance['free'] ?? $balance['amount'] ?? 0);
                    error_log("Direct query result #$idx: $balanceAsset - Free: $free");
                    
                    if ($balanceAsset === $assetUpper) {
                        $locked = floatval($balance['locked'] ?? 0);
                        $freeze = floatval($balance['freeze'] ?? 0);
                        $withdrawing = floatval($balance['withdrawing'] ?? 0);
                        $total = $free + $locked + $freeze + $withdrawing;
                        
                        error_log("✓✓✓ DIRECT QUERY FOUND $assetUpper!");
                        error_log("  Free: $free, Locked: $locked, Freeze: $freeze, Withdrawing: $withdrawing");
                        error_log("  Total (all states): $total");
                        error_log("  Full data: " . json_encode($balance));
                        
                        if ($total > 0) {
                            return [
                                'success' => true,
                                'asset' => $balance['asset'] ?? $balance['coin'] ?? $asset,
                                'free' => $free,
                                'locked' => $locked,
                                'total' => $total,
                                'account_type' => 'funding'
                            ];
                        } else {
                            error_log("⚠ Direct query found $assetUpper but balance is 0 - will continue searching spot account");
                        }
                    }
                }
            }
        } else {
            error_log("Direct query error or empty: " . json_encode($directBalanceResponse));
        }
        
        // Method 2: Get all assets (most comprehensive)
        error_log("Method 2: Getting ALL assets from funding account...");
        $balanceResponse = $this->signedRequest('GET', '/sapi/v1/asset/getUserAsset', []);
        
        if (isset($balanceResponse['error'])) {
            error_log("ERROR getting all assets: " . json_encode($balanceResponse['error']));
            $balanceResponse = [];
        }
        
        if (isset($balanceResponse['error'])) {
            error_log("Both methods failed. Error: " . json_encode($balanceResponse['error']));
            return [
                'success' => true,
                'asset' => $asset,
                'free' => 0,
                'locked' => 0,
                'total' => 0,
                'account_type' => 'funding_error'
            ];
        }
        
        // Log balance response (first 2000 chars to see structure)
        $responsePreview = json_encode($balanceResponse);
        error_log("Asset balance API response for $assetUpper (first 2000 chars): " . substr($responsePreview, 0, 2000));
        
        // Response can be array of assets or single object
        $balances = is_array($balanceResponse) ? $balanceResponse : [$balanceResponse];
        
        // If it's an associative array with 'asset' key, it's a single asset
        if (!isset($balances[0]) && isset($balanceResponse['asset'])) {
            $balances = [$balanceResponse];
        }
        
        error_log("Processing " . count($balances) . " asset(s) from response");
        
        // Log ALL assets found to help debug
        $allAssetNames = [];
        foreach ($balances as $idx => $balance) {
            if (is_array($balance)) {
                $assetName = strtoupper($balance['asset'] ?? $balance['coin'] ?? 'unknown');
                $free = floatval($balance['free'] ?? $balance['amount'] ?? 0);
                $allAssetNames[] = "$assetName=$free";
                
                // Log ALL assets (especially looking for USDT)
                if ($idx < 100 || stripos($assetName, 'USD') !== false || stripos($assetName, 'USDT') !== false || stripos($assetName, 'T') !== false) {
                    error_log("Asset #$idx: $assetName - Free: $free");
                }
                
                // Specifically look for USDT in any form
                if (stripos($assetName, 'USDT') !== false || $assetName === 'USDT') {
                    error_log("*** POTENTIAL USDT MATCH #$idx: $assetName = Free: $free, Full data: " . json_encode($balance));
                }
            }
        }
        error_log("=== ALL ASSETS FOUND IN FUNDING (" . count($allAssetNames) . " total) ===");
        error_log("Assets: " . implode(', ', array_slice($allAssetNames, 0, 200)));
        if (count($allAssetNames) > 200) {
            error_log("... and " . (count($allAssetNames) - 200) . " more assets");
        }
        
        // Check if asset is in the list at all (even with zero balance)
        $hasAsset = false;
        foreach ($allAssetNames as $assetEntry) {
            if (stripos($assetEntry, $assetUpper) !== false) {
                $hasAsset = true;
                error_log("*** $assetUpper FOUND IN ASSET LIST: $assetEntry");
                break;
            }
        }
        
        if (!$hasAsset) {
            error_log("*** WARNING: $assetUpper NOT IN THE ASSET LIST AT ALL!");
        }
        
        // Find the exact asset match (case-sensitive check)
        foreach ($balances as $balance) {
            if (!is_array($balance)) {
                continue;
            }
            
            $balanceAsset = strtoupper($balance['asset'] ?? $balance['coin'] ?? '');
            
            // CRITICAL: Only match exact asset name
            if ($balanceAsset === $assetUpper) {
                $free = floatval($balance['free'] ?? $balance['amount'] ?? 0);
                $locked = floatval($balance['locked'] ?? 0);
                $freeze = floatval($balance['freeze'] ?? 0);
                $withdrawing = floatval($balance['withdrawing'] ?? 0);
                $total = $free + $locked + $freeze + $withdrawing;
                
                error_log("✓✓✓ FOUND EXACT MATCH for $assetUpper in funding!");
                error_log("  Free: $free, Locked: $locked, Freeze: $freeze, Withdrawing: $withdrawing");
                error_log("  Total (all states): $total");
                error_log("  Full balance data: " . json_encode($balance));
                
                return [
                    'success' => true,
                    'asset' => $balance['asset'] ?? $balance['coin'] ?? $asset,
                    'free' => $free,
                    'locked' => $locked,
                    'total' => $total,  // Include all states
                    'account_type' => 'funding'
                ];
            }
        }
        
        // List all unique assets found
        $uniqueAssets = array_unique(array_map(function($b) {
            return strtoupper($b['asset'] ?? $b['coin'] ?? 'unknown');
        }, array_filter($balances, 'is_array')));
        error_log("✗ $assetUpper NOT FOUND in getUserAsset response!");
        error_log("Available unique assets (" . count($uniqueAssets) . "): " . implode(', ', array_slice($uniqueAssets, 0, 100)));
        
        // Try alternative: Check capital config (shows all available assets)
        error_log("=== TRYING ALTERNATIVE ENDPOINTS TO FIND $assetUpper ===");
        
        // Method 1: capital/config/getall
        error_log("Alternative Method 1: capital/config/getall...");
        $configResponse = $this->signedRequest('GET', '/sapi/v1/capital/config/getall', []);
        
        if (!isset($configResponse['error']) && is_array($configResponse)) {
            error_log("Capital config returned " . count($configResponse) . " assets");
            
            // Search through ALL assets (not just first 20)
            $configAssets = [];
            $usdtFound = false;
            
            foreach ($configResponse as $idx => $config) {
                $coinName = strtoupper($config['coin'] ?? '');
                $free = floatval($config['free'] ?? 0);
                $locked = floatval($config['locked'] ?? 0);
                
                // Log all USD-related assets
                if (stripos($coinName, 'USD') !== false || stripos($coinName, 'USDT') !== false) {
                    error_log("Capital Config Asset #$idx: $coinName - Free: $free, Locked: $locked");
                }
                
                $configAssets[] = "$coinName=$free";
                
                // Check for exact match
                if ($coinName === $assetUpper) {
                    $usdtFound = true;
                    error_log("✓✓✓ FOUND $assetUpper in capital config at index #$idx!");
                    error_log("  Free: $free, Locked: $locked, Total: " . ($free + $locked));
                    error_log("  Full config: " . json_encode($config));
                    
                    if ($free > 0 || $locked > 0) {
                        return [
                            'success' => true,
                            'asset' => $config['coin'] ?? $asset,
                            'free' => $free,
                            'locked' => $locked,
                            'total' => $free + $locked,
                            'account_type' => 'funding'
                        ];
                    }
                }
            }
            
            error_log("Total assets in capital config: " . count($configResponse));
            error_log("All assets (first 100): " . implode(', ', array_slice($configAssets, 0, 100)));
            
            if (!$usdtFound) {
                error_log("✗ $assetUpper NOT found in capital config at all!");
                // Search for any asset containing "USD" or "T"
                $usdRelated = array_filter($configAssets, function($a) {
                    return stripos($a, 'USD') !== false || stripos($a, 'T=') !== false;
                });
                if (!empty($usdRelated)) {
                    error_log("USD-related assets found: " . implode(', ', array_slice($usdRelated, 0, 20)));
                }
            }
        }
        
        // Method 2: Try wallet/balance endpoint
        error_log("Alternative Method 2: asset/wallet/balance...");
        try {
            $walletResponse = $this->signedRequest('GET', '/sapi/v1/asset/wallet/balance', []);
            if (!isset($walletResponse['error']) && is_array($walletResponse)) {
                error_log("Wallet balance returned " . count($walletResponse) . " items");
                
                foreach ($walletResponse as $wallet) {
                    $coinName = strtoupper($wallet['asset'] ?? $wallet['coin'] ?? '');
                    if ($coinName === $assetUpper) {
                        $free = floatval($wallet['free'] ?? $wallet['freeBalance'] ?? 0);
                        $locked = floatval($wallet['locked'] ?? $wallet['lockedBalance'] ?? 0);
                        
                        error_log("✓✓✓ FOUND $assetUpper in wallet/balance!");
                        error_log("  Free: $free, Locked: $locked, Total: " . ($free + $locked));
                        
                        if ($free > 0 || $locked > 0) {
                            return [
                                'success' => true,
                                'asset' => $wallet['asset'] ?? $wallet['coin'] ?? $asset,
                                'free' => $free,
                                'locked' => $locked,
                                'total' => $free + $locked,
                                'account_type' => 'funding'
                            ];
                        }
                    }
                }
            }
        } catch (Exception $e) {
            error_log("wallet/balance endpoint failed: " . $e->getMessage());
        }
        
        error_log("✗✗✗ $assetUpper NOT FOUND in any endpoint with non-zero balance!");
        error_log("============================================");
        error_log("USDT BALANCE CHECK SUMMARY:");
        error_log("  - Funding account: Checked, USDT not in asset list (only shows assets with balance > 0)");
        error_log("  - Spot account: USDT found but balance = 0");
        error_log("  - Capital config: USDT exists but balance = 0");
        error_log("  - Direct query: Attempted");
        error_log("  - Wallet balance: Checked");
        error_log("============================================");
        error_log("RECOMMENDATION: Please verify manually in Binance:");
        error_log("  1. Open Binance > Wallet > Funding");
        error_log("  2. Check if USDT balance shows > 0");
        error_log("  3. Verify API key has 'Enable Reading' permission for Spot & Funding");
        error_log("  4. Check if USDT might be in Margin/Futures (we don't check those)");
        error_log("  5. If balance shows in Binance but API shows 0, check API key permissions");
        error_log("============================================");
        return [
            'success' => true,
            'asset' => $asset,
            'free' => 0,
            'locked' => 0,
            'total' => 0,
            'account_type' => 'funding_not_found'
        ];
    }
    
    /**
     * Get trading pair information (for precision and filters)
     * @param string $symbol Trading pair (e.g., 'USDCUSDT')
     * @return array Response with trading pair info
     */
    public function getExchangeInfo($symbol) {
        $symbolUpper = strtoupper($symbol);
        
        // ExchangeInfo endpoint doesn't require signature, use regular GET
        $url = $this->baseUrl . '/api/v3/exchangeInfo?symbol=' . urlencode($symbolUpper);
        
        $ch = curl_init($url);
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
        curl_setopt($ch, CURLOPT_HTTPHEADER, [
            'Content-Type: application/json'
        ]);
        
        $response = curl_exec($ch);
        $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
        curl_close($ch);
        
        if ($httpCode !== 200) {
            error_log("ExchangeInfo API returned HTTP $httpCode");
            return ['success' => false, 'error' => "HTTP $httpCode"];
        }
        
        $data = json_decode($response, true);
        
        if (!$data || isset($data['code'])) {
            return ['success' => false, 'error' => $data['msg'] ?? 'Invalid response'];
        }
        
        // Find the symbol in the response
        if (isset($data['symbols']) && is_array($data['symbols'])) {
            foreach ($data['symbols'] as $symbolInfo) {
                if (strtoupper($symbolInfo['symbol'] ?? '') === $symbolUpper) {
                    return [
                        'success' => true,
                        'symbol' => $symbolInfo['symbol'],
                        'status' => $symbolInfo['status'],
                        'baseAsset' => $symbolInfo['baseAsset'],
                        'quoteAsset' => $symbolInfo['quoteAsset'],
                        'filters' => $symbolInfo['filters'] ?? []
                    ];
                }
            }
        }
        
        return ['success' => false, 'error' => 'Symbol not found'];
    }
    
    /**
     * Get Convert API quote
     * @param string $fromAsset Source asset (e.g., 'USDT')
     * @param string $toAsset Destination asset (e.g., 'USDC')
     * @param float $fromAmount Amount to convert (optional, either fromAmount or toAmount required)
     * @param float $toAmount Target amount (optional, either fromAmount or toAmount required)
     * @param string $walletType Wallet type: SPOT, FUNDING, EARN, or combinations (default: SPOT)
     * @return array Response with quoteId, ratio, toAmount, fromAmount
     */
    public function getConvertQuote($fromAsset, $toAsset, $fromAmount = null, $toAmount = null, $walletType = 'SPOT') {
        if ($fromAmount === null && $toAmount === null) {
            return ['error' => 'Either fromAmount or toAmount must be provided'];
        }
        
        $params = [
            'fromAsset' => strtoupper($fromAsset),
            'toAsset' => strtoupper($toAsset),
            'walletType' => $walletType
        ];
        
        if ($fromAmount !== null) {
            $params['fromAmount'] = $fromAmount;
        }
        if ($toAmount !== null) {
            $params['toAmount'] = $toAmount;
        }
        
        return $this->signedRequest('POST', '/sapi/v1/convert/getQuote', $params);
    }
    
    /**
     * Accept Convert API quote
     * @param string $quoteId Quote ID from getConvertQuote
     * @return array Response with orderId, createTime, orderStatus
     */
    public function acceptConvertQuote($quoteId) {
        $params = [
            'quoteId' => $quoteId
        ];
        
        return $this->signedRequest('POST', '/sapi/v1/convert/acceptQuote', $params);
    }
    
    /**
     * Get Convert order status
     * @param string $orderId Order ID (optional, either orderId or quoteId required)
     * @param string $quoteId Quote ID (optional, either orderId or quoteId required)
     * @return array Response with orderId, orderStatus, fromAsset, fromAmount, toAsset, toAmount, ratio
     */
    public function getConvertOrderStatus($orderId = null, $quoteId = null) {
        if ($orderId === null && $quoteId === null) {
            return ['error' => 'Either orderId or quoteId must be provided'];
        }
        
        $params = [];
        if ($orderId !== null) {
            $params['orderId'] = $orderId;
        }
        if ($quoteId !== null) {
            $params['quoteId'] = $quoteId;
        }
        
        return $this->signedRequest('GET', '/sapi/v1/convert/orderStatus', $params);
    }
    
    /**
     * Get Convert trade history
     * @param int $startTime Start time in milliseconds
     * @param int $endTime End time in milliseconds
     * @param int $limit Limit (default 100, max 1000)
     * @return array Response with list of convert trades
     */
    public function getConvertTradeHistory($startTime, $endTime, $limit = 100) {
        $params = [
            'startTime' => $startTime,
            'endTime' => $endTime,
            'limit' => min($limit, 1000) // Max 1000
        ];
        
        return $this->signedRequest('GET', '/sapi/v1/convert/tradeFlow', $params);
    }
    
    /**
     * Execute a spot trade (market order)
     * @param string $symbol Trading pair (e.g., 'USDCUSDT')
     * @param string $side 'BUY' or 'SELL'
     * @param float $quantity Quantity to trade (in USDT if buying USDC)
     * @return array Response with order information
     */
    public function placeMarketOrder($symbol, $side, $quantity) {
        // Ensure symbol is uppercase and doesn't have separator
        $symbol = strtoupper(str_replace(['/', '_', '-'], '', $symbol));
        
        // For USDT to USDC conversion, the correct symbol is USDCUSDT
        if (!preg_match('/USDT$|USDC$/', $symbol)) {
            $symbol = 'USDCUSDT';
        } elseif (preg_match('/USDTUSDC$/', $symbol)) {
            $symbol = 'USDCUSDT';
        }
        
        // Get exchange info to get proper precision
        error_log("Getting exchange info for $symbol...");
        $exchangeInfo = $this->getExchangeInfo($symbol);
        
        $minQty = 1.0;
        $stepSize = 0.000001;
        $minNotional = 1.0;
        
        if (isset($exchangeInfo['filters']) && is_array($exchangeInfo['filters'])) {
            foreach ($exchangeInfo['filters'] as $filter) {
                if ($filter['filterType'] === 'LOT_SIZE') {
                    $minQty = floatval($filter['minQty'] ?? 1.0);
                    $stepSize = floatval($filter['stepSize'] ?? 0.000001);
                    error_log("LOT_SIZE filter: minQty=$minQty, stepSize=$stepSize");
                } elseif ($filter['filterType'] === 'MIN_NOTIONAL') {
                    $minNotional = floatval($filter['minNotional'] ?? 1.0);
                    error_log("MIN_NOTIONAL filter: $minNotional");
                }
            }
        }
        
        // If selling USDT to buy USDC, use quoteOrderQty (amount in USDT to spend)
        if ($side === 'SELL' && $symbol === 'USDCUSDT') {
            $side = 'BUY';
            
            // Round quoteOrderQty to proper precision (usually 2 decimals for USDT)
            $quoteOrderQty = round($quantity, 2);
            
            // Ensure it meets minimum notional
            if ($quoteOrderQty < $minNotional) {
                return [
                    'error' => "Quantity $quoteOrderQty USDT is below minimum notional of $minNotional USDT"
                ];
            }
            
            $params = [
                'symbol' => $symbol,
                'side' => $side,
                'type' => 'MARKET',
                'quoteOrderQty' => $quoteOrderQty
            ];
        } else {
            // For other orders, round quantity to stepSize precision
            $precision = strlen(rtrim(str_replace('.', '', number_format($stepSize, 10)), '0'));
            $roundedQuantity = round($quantity / $stepSize) * $stepSize;
            $roundedQuantity = round($roundedQuantity, $precision);
            
            // Ensure it meets minimum quantity
            if ($roundedQuantity < $minQty) {
                return [
                    'error' => "Quantity $roundedQuantity is below minimum of $minQty"
                ];
            }
            
            $params = [
                'symbol' => $symbol,
                'side' => strtoupper($side),
                'type' => 'MARKET',
                'quantity' => $roundedQuantity
            ];
        }
        
        error_log("Placing market order - Symbol: $symbol, Side: " . $params['side'] . ", Params: " . json_encode($params));
        
        return $this->signedRequest('POST', '/api/v3/order', $params);
    }
    
    /**
     * Withdraw cryptocurrency to external address
     * @param string $asset Asset to withdraw (e.g., 'USDC')
     * @param string $address Destination address
     * @param float $amount Amount to withdraw
     * @param string $network Network (e.g., 'MATIC' for Polygon, 'POLYGON' will be converted to 'MATIC')
     * @param string $addressTag Optional address tag/memo
     * @return array Response with withdrawal information
     */
    public function withdraw($asset, $address, $amount, $network = 'POLYGON', $addressTag = '') {
        // Convert POLYGON to MATIC (Binance uses MATIC as network name for Polygon)
        $networkUpper = strtoupper($network);
        if ($networkUpper === 'POLYGON') {
            $networkUpper = 'MATIC';
            error_log("Converting network name from POLYGON to MATIC (Binance standard)");
        }
        
        $params = [
            'coin' => strtoupper($asset),
            'address' => $address,
            'amount' => $amount,
            'network' => $networkUpper
        ];
        
        if (!empty($addressTag)) {
            $params['addressTag'] = $addressTag;
        }
        
        // Withdrawal endpoint requires special handling
        return $this->signedRequest('POST', '/sapi/v1/capital/withdraw/apply', $params);
    }
    
    /**
     * Get withdrawal history
     * @param string $asset Optional asset filter
     * @param int $limit Optional limit (default 10)
     * @return array Response with withdrawal history
     */
    public function getWithdrawalHistory($asset = '', $limit = 10) {
        $params = ['limit' => $limit];
        if (!empty($asset)) {
            $params['coin'] = strtoupper($asset);
        }
        
        return $this->signedRequest('GET', '/sapi/v1/capital/withdraw/history', $params);
    }
    
    /**
     * Get all balances across all account types (for debugging)
     * @return array All balances found (including zero balances for USDT)
     */
    public function getAllBalances() {
        $allBalances = [];
        
        // Get funding account balances - get ALL assets, including zeros
        $fundingResponse = $this->signedRequest('GET', '/sapi/v1/asset/getUserAsset', []);
        if (!isset($fundingResponse['error']) && is_array($fundingResponse)) {
            error_log("Funding account returned " . count($fundingResponse) . " assets");
            
            foreach ($fundingResponse as $asset) {
                $assetName = $asset['asset'] ?? $asset['coin'] ?? 'unknown';
                $free = floatval($asset['free'] ?? $asset['amount'] ?? 0);
                $locked = floatval($asset['locked'] ?? 0);
                
                // Include ALL assets, especially USDT even if zero
                $allBalances[] = [
                    'asset' => $assetName,
                    'free' => $free,
                    'locked' => $locked,
                    'total' => $free + $locked,
                    'account' => 'funding'
                ];
                
                // Log USDT specifically
                if (strtoupper($assetName) === 'USDT') {
                    error_log("FOUND USDT in funding: Free=$free, Locked=$locked, Total=" . ($free + $locked));
                }
            }
        } else {
            error_log("Error getting funding assets: " . json_encode($fundingResponse['error'] ?? 'Unknown'));
        }
        
        // Get spot account balances - get ALL, including zeros
        $spotResponse = $this->signedRequest('GET', '/api/v3/account');
        if (!isset($spotResponse['error']) && isset($spotResponse['balances'])) {
            error_log("Spot account returned " . count($spotResponse['balances']) . " balances");
            
            foreach ($spotResponse['balances'] as $balance) {
                $assetName = $balance['asset'] ?? 'unknown';
                $free = floatval($balance['free'] ?? 0);
                $locked = floatval($balance['locked'] ?? 0);
                
                // Only add if not already in list or if it's USDT
                $found = false;
                foreach ($allBalances as $existing) {
                    if ($existing['asset'] === $assetName && $existing['account'] === 'spot') {
                        $found = true;
                        break;
                    }
                }
                
                if (!$found) {
                    $allBalances[] = [
                        'asset' => $assetName,
                        'free' => $free,
                        'locked' => $locked,
                        'total' => $free + $locked,
                        'account' => 'spot'
                    ];
                    
                    // Log USDT specifically
                    if (strtoupper($assetName) === 'USDT') {
                        error_log("FOUND USDT in spot: Free=$free, Locked=$locked, Total=" . ($free + $locked));
                    }
                }
            }
        } else {
            error_log("Error getting spot account: " . json_encode($spotResponse['error'] ?? 'Unknown'));
        }
        
        // Search specifically for USDT in all accounts
        error_log("=== SEARCHING FOR USDT SPECIFICALLY ===");
        $usdtEntries = array_filter($allBalances, function($b) {
            return strtoupper($b['asset'] ?? '') === 'USDT';
        });
        
        if (empty($usdtEntries)) {
            error_log("WARNING: USDT NOT FOUND in any account!");
            error_log("All unique assets found: " . implode(', ', array_unique(array_map(function($b) {
                return $b['asset'];
            }, $allBalances))));
        } else {
            error_log("USDT entries found: " . count($usdtEntries));
            foreach ($usdtEntries as $usdt) {
                error_log("  - " . $usdt['account'] . ": Free=" . $usdt['free'] . ", Locked=" . $usdt['locked']);
            }
        }
        
        return $allBalances;
    }
    
    /**
     * Transfer asset from funding account to spot account
     * @param string $asset Asset to transfer (e.g., 'USDT')
     * @param float $amount Amount to transfer
     * @return array Response with transfer information
     */
    public function transferFundingToSpot($asset, $amount) {
        // Binance transfer API: type parameter is an ENUM string
        // FUNDING_MAIN = Funding account transfer to Spot account
        
        // Round DOWN amount to proper precision to avoid "insufficient balance" errors
        // USDC uses 6 decimals, USDT uses 2 decimals
        // Using floor() ensures we never try to transfer more than available
        $assetUpper = strtoupper($asset);
        $precision = ($assetUpper === 'USDC') ? 6 : 2;
        $multiplier = pow(10, $precision);
        $roundedAmount = floor($amount * $multiplier) / $multiplier;
        
        $params = [
            'asset' => $assetUpper,
            'amount' => $roundedAmount,
            'type' => 'FUNDING_MAIN' // Transfer from FUNDING to SPOT (MAIN)
        ];
        
        error_log("Transferring $roundedAmount $assetUpper from funding to spot account...");
        error_log("Transfer params: " . json_encode($params));
        $result = $this->signedRequest('POST', '/sapi/v1/asset/transfer', $params);
        
        if (isset($result['error'])) {
            $errorMsg = is_array($result['error']) ? json_encode($result['error']) : $result['error'];
            error_log("Transfer error: " . $errorMsg);
            
            // Check for specific error codes
            if (isset($result['code'])) {
                if ($result['code'] == -1000) {
                    error_log("ERROR: API key may not have 'Permits Universal Transfer' permission enabled.");
                    error_log("Please enable this permission in Binance API Management: https://www.binance.com/en/my/settings/api-management");
                }
            }
        } else {
            error_log("Transfer successful: " . json_encode($result));
        }
        
        return $result;
    }
}

