<?php

namespace App\Console\Commands;

use Illuminate\Console\Command;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\Log;
use App\TradingPair;

class StreamTradingData extends Command
{
    protected $signature = 'trading:stream';
    protected $description = 'Stream real-time trading data from CoinEx WebSocket';

    protected $socket;
    protected $markets = [];

    public function handle()
    {
        $this->info('Starting CoinEx Trading Data Stream...');

        // Get active trading pairs
        $tradingPairs = TradingPair::where('is_active', true)->get();
        
        if ($tradingPairs->isEmpty()) {
            $this->error('No active trading pairs found. Please add trading pairs first.');
            return;
        }

        foreach ($tradingPairs as $pair) {
            $this->markets[] = $pair->market;
        }

        $this->info('Streaming markets: ' . implode(', ', $this->markets));

        // Connect to WebSocket
        $this->connectWebSocket();
    }

    protected function connectWebSocket()
    {
        $host = 'wss://socket.coinex.com/v2/spot';
        
        $context = stream_context_create([
            'ssl' => [
                'verify_peer' => false,
                'verify_peer_name' => false,
            ]
        ]);

        $this->socket = @stream_socket_client(
            $host,
            $errno,
            $errstr,
            30,
            STREAM_CLIENT_CONNECT,
            $context
        );

        if (!$this->socket) {
            $this->error("WebSocket connection failed: $errstr ($errno)");
            return;
        }

        $this->info('WebSocket connected!');

        // Perform WebSocket handshake
        $this->performHandshake($host);

        // Subscribe to channels
        $this->subscribe();

        // Listen for messages
        $this->listen();
    }

    protected function performHandshake($host)
    {
        $key = base64_encode(random_bytes(16));
        
        $headers = [
            "GET /v2/spot HTTP/1.1",
            "Host: socket.coinex.com",
            "Upgrade: websocket",
            "Connection: Upgrade",
            "Sec-WebSocket-Key: $key",
            "Sec-WebSocket-Version: 13",
            "Origin: https://www.coinex.com",
        ];

        $request = implode("\r\n", $headers) . "\r\n\r\n";
        fwrite($this->socket, $request);

        // Read response
        $response = '';
        while ($line = fgets($this->socket)) {
            $response .= $line;
            if (trim($line) === '') break;
        }

        if (strpos($response, '101 Switching Protocols') === false) {
            $this->error('WebSocket handshake failed');
            return false;
        }

        $this->info('WebSocket handshake successful');
        return true;
    }

    protected function subscribe()
    {
        // Subscribe to ticker for all markets
        foreach ($this->markets as $market) {
            $this->subscribeToChannel('ticker', [$market]);
            $this->subscribeToChannel('depth', [$market, 20, '0']);
        }

        $this->info('Subscribed to all channels');
    }

    protected function subscribeToChannel($method, $params)
    {
        $message = [
            'method' => "state.subscribe",
            'params' => [
                'market_list' => $params
            ],
            'id' => time()
        ];

        $this->sendMessage($message);
    }

    protected function sendMessage($data)
    {
        $json = json_encode($data);
        $frame = $this->encodeFrame($json);
        fwrite($this->socket, $frame);
    }

    protected function encodeFrame($payload)
    {
        $length = strlen($payload);
        $frame = chr(0x81); // Text frame

        if ($length <= 125) {
            $frame .= chr($length | 0x80);
        } elseif ($length <= 65535) {
            $frame .= chr(126 | 0x80);
            $frame .= pack('n', $length);
        } else {
            $frame .= chr(127 | 0x80);
            $frame .= pack('J', $length);
        }

        // Masking key
        $mask = random_bytes(4);
        $frame .= $mask;

        // Mask payload
        for ($i = 0; $i < $length; $i++) {
            $frame .= $payload[$i] ^ $mask[$i % 4];
        }

        return $frame;
    }

    protected function listen()
    {
        $this->info('Listening for messages...');

        while (!feof($this->socket)) {
            $data = $this->readFrame();
            
            if ($data === null) {
                continue;
            }

            $message = json_decode($data, true);
            
            if (!$message) {
                continue;
            }

            // Handle different message types
            if (isset($message['method'])) {
                $this->handleMessage($message);
            }

            // Send ping every 30 seconds
            static $lastPing = 0;
            if (time() - $lastPing > 30) {
                $this->sendPing();
                $lastPing = time();
            }
        }

        $this->error('WebSocket connection closed');
    }

    protected function readFrame()
    {
        $header = fread($this->socket, 2);
        if (strlen($header) < 2) {
            return null;
        }

        $byte1 = ord($header[0]);
        $byte2 = ord($header[1]);

        $opcode = $byte1 & 0x0F;
        $masked = ($byte2 & 0x80) !== 0;
        $length = $byte2 & 0x7F;

        if ($length === 126) {
            $length = unpack('n', fread($this->socket, 2))[1];
        } elseif ($length === 127) {
            $length = unpack('J', fread($this->socket, 8))[1];
        }

        if ($masked) {
            $mask = fread($this->socket, 4);
        }

        $payload = '';
        if ($length > 0) {
            $payload = fread($this->socket, $length);
        }

        if ($masked && strlen($payload) > 0) {
            for ($i = 0; $i < strlen($payload); $i++) {
                $payload[$i] = $payload[$i] ^ $mask[$i % 4];
            }
        }

        // Handle ping/pong
        if ($opcode === 0x9) {
            $this->sendPong($payload);
            return null;
        }

        return $payload;
    }

    protected function sendPing()
    {
        $message = ['method' => 'server.ping', 'params' => [], 'id' => time()];
        $this->sendMessage($message);
    }

    protected function sendPong($data)
    {
        $frame = chr(0x8A) . chr(strlen($data)) . $data;
        fwrite($this->socket, $frame);
    }

    protected function handleMessage($message)
    {
        $method = $message['method'] ?? null;

        if ($method === 'state.update') {
            $this->handleStateUpdate($message);
        } elseif ($method === 'depth.update') {
            $this->handleDepthUpdate($message);
        }
    }

    protected function handleStateUpdate($message)
    {
        // Ticker update
        if (!isset($message['params']) || !is_array($message['params'])) {
            return;
        }

        foreach ($message['params'] as $market => $data) {
            if (!isset($data['last'])) {
                continue;
            }

            $priceData = [
                'market' => $market,
                'last' => $data['last'],
                'open' => $data['open'] ?? 0,
                'high' => $data['high'] ?? 0,
                'low' => $data['low'] ?? 0,
                'volume' => $data['volume'] ?? 0,
                'change' => isset($data['open']) && $data['open'] > 0 
                    ? (($data['last'] - $data['open']) / $data['open']) * 100 
                    : 0,
                'updated_at' => time(),
            ];

            Cache::put("ticker:$market", $priceData, 60);
            
            $this->line("Ticker: $market = {$data['last']}");
        }
    }

    protected function handleDepthUpdate($message)
    {
        // Order book update
        if (!isset($message['params'])) {
            return;
        }

        $market = $message['params']['market'] ?? null;
        $asks = $message['params']['asks'] ?? [];
        $bids = $message['params']['bids'] ?? [];

        if (!$market) {
            return;
        }

        $orderBook = [
            'market' => $market,
            'asks' => array_slice($asks, 0, 20),
            'bids' => array_slice($bids, 0, 20),
            'updated_at' => time(),
        ];

        Cache::put("orderbook:$market", $orderBook, 60);
        
        $this->line("OrderBook: $market updated");
    }
}
