<?php
/**
 * Binance WebSocket Chat Client
 * Handles WebSocket connections to Binance chat system
 * Uses Ratchet/Pawl for WebSocket client
 */

require_once __DIR__ . '/../vendor/autoload.php';

use Ratchet\Client\WebSocket;
use Ratchet\Client\Connector;
use React\EventLoop\Factory;

class BinanceWebSocketClient {
    private $wssUrl;
    private $listenKey;
    private $listenToken;
    private $loop;
    private $connector;
    private $connection;
    private $connected = false;
    private $ready = false;
    private $messageQueue = [];
    
    public function __construct($wssUrl, $listenKey, $listenToken) {
        $this->wssUrl = $wssUrl;
        $this->listenKey = $listenKey;
        // Use token as-is (Binance docs show token should include TOKEN prefix)
        $this->listenToken = $listenToken;
        $this->loop = Factory::create();
        $this->connector = new Connector($this->loop);
    }
    
    /**
     * Connect to WebSocket
     */
    public function connect() {
        // URL encode the token to ensure special characters are handled correctly
        $encodedToken = urlencode($this->listenToken);
        
        // Build WebSocket URL - ensure proper URL encoding
        $wsUrl = $this->wssUrl . "/" . $this->listenKey . "?token=" . $encodedToken . "&clientType=web";
        
        error_log("Connecting to WebSocket: " . str_replace($this->listenToken, 'TOKEN***', $wsUrl));
        error_log("Token length: " . strlen($this->listenToken) . " chars");
        error_log("ListenKey length: " . strlen($this->listenKey) . " chars");
        
        // Connector is callable - invoke it
        $promise = call_user_func($this->connector, $wsUrl);
        
        $promise->then(function(WebSocket $conn) {
            $this->connection = $conn;
            $this->connected = true;
            error_log("WebSocket connected successfully");
            
            // Track if we received an error
            $errorReceived = false;
            
            // Handle incoming messages
            $conn->on('message', function($msg) use (&$errorReceived) {
                $data = json_decode($msg, true);
                if ($data && isset($data['type']) && $data['type'] === 'error') {
                    $errorReceived = true;
                    error_log("ERROR: Received error message immediately after connection: " . json_encode($data));
                    error_log("This suggests the connection URL parameters are invalid");
                    // Don't mark as ready if we got an error
                    $this->connected = false;
                    $this->ready = false;
                }
                $this->handleMessage($msg);
            });
            
            // Handle connection close
            $conn->on('close', function($code = null, $reason = null) {
                error_log("WebSocket closed. Code: $code, Reason: $reason");
                $this->connected = false;
                $this->ready = false;
            });
            
            // Handle errors
            $conn->on('error', function($error) {
                error_log("WebSocket error: " . $error->getMessage());
                $this->connected = false;
                $this->ready = false;
            });
            
            // Wait a moment for connection to stabilize and check for initial messages
            // Binance might send connection confirmation or error immediately
            $this->loop->addTimer(2, function() use (&$errorReceived) {
                // Only mark as ready if we haven't received an error and still connected
                if ($this->connected && !$errorReceived) {
                    $this->ready = true;
                    error_log("WebSocket ready to send messages");
                    // Send any queued messages
                    $this->processMessageQueue();
                } else {
                    if ($errorReceived) {
                        error_log("WebSocket received error, not marking as ready");
                    } else {
                        error_log("WebSocket connection lost before ready");
                    }
                }
            });
            
        }, function(\Exception $e) {
            error_log("WebSocket connection failed: " . $e->getMessage());
            $this->connected = false;
            $this->ready = false;
            throw $e;
        });
        
        // Run event loop with timeout to establish connection
        // Set a timer to stop the loop after 10 seconds
        $this->loop->addTimer(10, function() {
            $this->loop->stop();
        });
        
        // Run event loop
        $this->loop->run();
    }
    
    /**
     * Send message via WebSocket
     * Format according to Binance documentation:
     * {
     *   "type":"text",
     *   "uuid":"1682102167020",
     *   "orderNo":"20476365073825144832",
     *   "content":"xxxxxx",
     *   "self":true,
     *   "clientType":"web",
     *   "createTime":{{timestamp}},
     *   "sendStatus":0
     * }
     */
    public function sendMessage($orderNo, $content) {
        // Ensure orderNo is a string
        $orderNoStr = (string)$orderNo;
        
        // Generate UUID (timestamp in milliseconds + random)
        $uuid = (string)(time() * 1000) . rand(1000, 9999);
        $createTime = time() * 1000;
        
        // Message format according to Binance documentation
        $messageData = [
            'type' => 'text',
            'uuid' => $uuid,
            'orderNo' => $orderNoStr,
            'content' => $content,
            'self' => true,
            'clientType' => 'web',
            'createTime' => $createTime,
            'sendStatus' => 0
        ];
        
        if (!$this->connected || !$this->connection) {
            // Queue message if not connected
            $this->messageQueue[] = ['orderNo' => $orderNoStr, 'content' => $content];
            error_log("WebSocket not connected, queuing message for order $orderNoStr");
            return false;
        }
        
        if (!$this->ready) {
            // Queue message if not ready yet
            $this->messageQueue[] = ['orderNo' => $orderNoStr, 'content' => $content];
            error_log("WebSocket not ready yet, queuing message for order $orderNoStr");
            return false;
        }
        
        try {
            // Send the message according to Binance format
            $jsonMessage = json_encode($messageData);
            error_log("Sending message to order $orderNoStr: " . substr($jsonMessage, 0, 300) . "...");
            $this->connection->send($jsonMessage);
            
            // Wait a moment to see if we get an error response
            usleep(200000); // 0.2 seconds
            
            error_log("Message sent via WebSocket for order $orderNoStr (no immediate error)");
            return true;
        } catch (\Exception $e) {
            error_log("Error sending WebSocket message: " . $e->getMessage());
            // Queue for retry
            $this->messageQueue[] = ['orderNo' => $orderNoStr, 'content' => $content];
            return false;
        }
    }
    
    /**
     * Process queued messages
     */
    private function processMessageQueue() {
        if (empty($this->messageQueue) || !$this->connected || !$this->ready) {
            return;
        }
        
        foreach ($this->messageQueue as $index => $queuedMessage) {
            try {
                $orderNo = $queuedMessage['orderNo'];
                $content = $queuedMessage['content'];
                
                // Rebuild message with Binance format
                $uuid = (string)(time() * 1000) . rand(1000, 9999);
                $createTime = time() * 1000;
                
                $messageData = [
                    'type' => 'text',
                    'uuid' => $uuid,
                    'orderNo' => $orderNo,
                    'content' => $content,
                    'self' => true,
                    'clientType' => 'web',
                    'createTime' => $createTime,
                    'sendStatus' => 0
                ];
                
                $jsonMessage = json_encode($messageData);
                $this->connection->send($jsonMessage);
                unset($this->messageQueue[$index]);
                error_log("Sent queued message for order: $orderNo");
                
                // Small delay between messages to avoid rate limiting
                usleep(500000); // 0.5 seconds
            } catch (\Exception $e) {
                error_log("Error sending queued message: " . $e->getMessage());
            }
        }
        
        // Re-index array
        $this->messageQueue = array_values($this->messageQueue);
    }
    
    /**
     * Handle incoming messages
     */
    private function handleMessage($msg) {
        $data = json_decode($msg, true);
        if ($data) {
            error_log("Received WebSocket message: " . json_encode($data));
            
            // Check for error messages
            if (isset($data['type']) && $data['type'] === 'error') {
                error_log("WebSocket error received: " . ($data['content'] ?? 'Unknown error'));
                // Don't mark as not ready on error, might be a message-specific error
            }
            
            // If we receive a successful message acknowledgment, we know connection is working
            if (isset($data['type']) && $data['type'] !== 'error') {
                // Connection seems healthy
            }
        }
    }
    
    /**
     * Close connection
     */
    public function close() {
        if ($this->connection) {
            $this->connection->close();
            $this->connected = false;
        }
        if ($this->loop) {
            $this->loop->stop();
        }
    }
    
    /**
     * Check if connected
     */
    public function isConnected() {
        return $this->connected;
    }
}

