<?php

namespace App\Services;

use Illuminate\Support\Facades\Http;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\Log;

class BinanceService
{
    // Binance API - Simple direct call (initial data load only)
    // Frontend uses WebSocket for real-time updates
    private const BASE_URL = 'https://api.binance.com/api/v3';
    private const CACHE_TTL = 30; // 30 seconds cache for initial data load

    /**
     * Get 24hr ticker price change statistics for a symbol
     */
    public function getTickerPriceChange(string $symbol): ?array
    {
        try {
            $cacheKey = "binance_ticker_{$symbol}";
            
            return Cache::remember($cacheKey, self::CACHE_TTL, function () use ($symbol) {
                // Simple direct Binance API call (like Demo-Skraken)
                // Support HTTP proxy if configured (for blocked regions)
                try {
                    $httpClient = Http::timeout(30)->withoutVerifying(); // Increased timeout for production
                    
                    // Use proxy if configured in .env
                    if (env('HTTP_PROXY') || env('HTTPS_PROXY')) {
                        $proxy = env('HTTPS_PROXY') ?: env('HTTP_PROXY');
                        $httpClient = $httpClient->withOptions([
                            'proxy' => $proxy
                        ]);
                    }
                    
                    // Try Binance API with retry logic
                    $maxRetries = 3;
                    $response = null;
                    $lastError = null;
                    
                    for ($attempt = 1; $attempt <= $maxRetries; $attempt++) {
                        try {
                            $url = self::BASE_URL . '/ticker/24hr';
                            Log::info("Binance API attempt {$attempt}/{$maxRetries} for {$symbol}");
                            
                            $response = $httpClient->get($url, [
                                'symbol' => $symbol
                            ]);
                            
                            if ($response->successful()) {
                                $data = $response->json();
                                
                                // Validate response data
                                if (isset($data['symbol']) && isset($data['lastPrice'])) {
                                    Log::info("✅ Binance API success for {$symbol}: price = " . $data['lastPrice']);
                                    return [
                                        'symbol' => $data['symbol'],
                                        'price' => (float) $data['lastPrice'],
                                        'priceChange' => (float) $data['priceChange'],
                                        'priceChangePercent' => (float) $data['priceChangePercent'],
                                        'highPrice' => (float) $data['highPrice'],
                                        'lowPrice' => (float) $data['lowPrice'],
                                        'volume' => (float) $data['volume'],
                                        'quoteVolume' => (float) $data['quoteVolume'],
                                    ];
                                } else {
                                    $lastError = "Invalid response data structure";
                                }
                            } else {
                                $lastError = "HTTP {$response->status()}: " . substr($response->body(), 0, 200);
                                Log::warning("Binance API HTTP error for {$symbol}: {$lastError}");
                            }
                        } catch (\Illuminate\Http\Client\ConnectionException $e) {
                            $lastError = "Connection error: " . $e->getMessage();
                            Log::warning("Binance API connection error (attempt {$attempt}/{$maxRetries}) for {$symbol}");
                            if ($attempt < $maxRetries) {
                                // Wait before retry (exponential backoff)
                                usleep(500000 * $attempt); // 0.5s, 1s, 1.5s
                            }
                        } catch (\Exception $e) {
                            $lastError = $e->getMessage();
                            Log::warning("Binance API exception (attempt {$attempt}/{$maxRetries}) for {$symbol}: " . substr($lastError, 0, 100));
                            if ($attempt < $maxRetries) {
                                usleep(500000 * $attempt);
                            }
                        }
                    }
                    
                    // All retries failed
                    Log::error("❌ Binance API failed after {$maxRetries} attempts for {$symbol}. Last error: " . substr($lastError, 0, 200));
                    
                } catch (\Exception $e) {
                    Log::error('Binance API exception: ' . $e->getMessage());
                }

                // DO NOT return mock data - return null instead
                // Frontend WebSocket will provide real-time data (client-side, bypasses server restrictions)
                Log::warning("Binance API unreachable for {$symbol}. Frontend WebSocket will provide real-time data.");
                return null; // Return null so frontend knows to wait for WebSocket data
                });
            } catch (\Exception $e) {
                Log::error('Binance API Error: ' . $e->getMessage());
                
                // DO NOT return mock data - return null instead
                // Frontend WebSocket will provide real-time data (client-side, bypasses server restrictions)
                Log::warning("Binance API exception for {$symbol}. Frontend WebSocket will provide real-time data.");
                return null; // Return null so frontend knows to wait for WebSocket data
            }
    }

    /**
     * Generate mock ticker data when Binance API is unreachable
     * Uses deterministic approach based on symbol and time for consistency
     */
    private function generateMockTicker(string $symbol): array
    {
        $basePrice = $this->getBasePriceForSymbol($symbol);
        
        // Use symbol hash + time (rounded to 5 minutes) for deterministic but changing values
        $timeSlot = floor(time() / 300); // 5-minute slots
        $seed = crc32($symbol . $timeSlot);
        
        // Generate consistent pseudo-random values based on seed
        mt_srand($seed);
        
        // Price change: -2% to +2% (more realistic range)
        $priceChangePercent = (mt_rand(-200, 200) / 100);
        $priceChange = $basePrice * ($priceChangePercent / 100);
        $currentPrice = $basePrice + $priceChange;
        
        // High/Low: within 1-3% of current price (realistic 24h range)
        $highPercent = 1 + (mt_rand(10, 30) / 1000); // 1% to 3% above
        $lowPercent = 1 - (mt_rand(10, 30) / 1000);  // 1% to 3% below
        
        // Volume based on symbol (larger coins have more volume)
        $volumeMultiplier = $basePrice > 1000 ? 1000000 : ($basePrice > 100 ? 500000 : 100000);
        $volume = mt_rand($volumeMultiplier, $volumeMultiplier * 10);
        $quoteVolume = $volume * $currentPrice;
        
        mt_srand(); // Reset seed
        
        // Return as floats (not strings) for better JSON encoding
        // MarketPriceController will cast to float anyway
        return [
            'symbol' => $symbol,
            'price' => round($currentPrice, 2),
            'priceChange' => round($priceChange, 2),
            'priceChangePercent' => round($priceChangePercent, 2),
            'highPrice' => round($currentPrice * $highPercent, 2),
            'lowPrice' => round($currentPrice * $lowPercent, 2),
            'volume' => round($volume, 2),
            'quoteVolume' => round($quoteVolume, 2),
        ];
    }

    /**
     * Get current price for a symbol
     */
    public function getCurrentPrice(string $symbol): ?float
    {
        try {
            $cacheKey = "binance_price_{$symbol}";
            
            return Cache::remember($cacheKey, self::CACHE_TTL, function () use ($symbol) {
                // Simple direct Binance API call (like Demo-Skraken)
                // Support HTTP proxy if configured (for blocked regions)
                try {
                    $httpClient = Http::timeout(30)->withoutVerifying(); // Increased timeout for production
                    
                    // Use proxy if configured in .env
                    if (env('HTTP_PROXY') || env('HTTPS_PROXY')) {
                        $proxy = env('HTTPS_PROXY') ?: env('HTTP_PROXY');
                        $httpClient = $httpClient->withOptions([
                            'proxy' => $proxy
                        ]);
                    }
                    
                    $response = $httpClient->get(self::BASE_URL . '/ticker/price', [
                        'symbol' => $symbol
                    ]);

                    if ($response->successful()) {
                        $data = $response->json();
                        return (float) $data['price'];
                    }
                } catch (\Exception $e) {
                    Log::warning('Binance API unreachable for price: ' . $e->getMessage());
                }

                // Return null on error
                return null;
            });
        } catch (\Exception $e) {
            Log::error('Binance API Error: ' . $e->getMessage());
            // DO NOT return mock data - return null
            // Frontend WebSocket will provide real-time data
            return null;
        }
    }

    /**
     * Get multiple ticker prices at once
     * Uses individual symbol requests for better performance
     */
    public function getMultipleTickers(array $symbols): array
    {
        try {
            $cacheKey = 'binance_tickers_' . md5(implode(',', $symbols));
            
            return Cache::remember($cacheKey, self::CACHE_TTL, function () use ($symbols) {
                $result = [];
                
                // Fetch each symbol individually for better performance and reliability
                foreach ($symbols as $symbol) {
                    try {
                        $ticker = $this->getTickerPriceChange($symbol);
                        if ($ticker !== null) {
                            $result[$symbol] = $ticker;
                        }
                    } catch (\Exception $e) {
                        // Continue with other symbols if one fails
                        Log::warning("Failed to fetch ticker for {$symbol}: " . $e->getMessage());
                    }
                }
                
                // DO NOT use mock data - return empty array if all fail
                // Frontend WebSocket will provide real-time data (client-side, bypasses server restrictions)
                if (empty($result)) {
                    Log::warning('Binance API failed for all symbols. Frontend WebSocket will provide real-time data.');
                    // Return empty array - frontend WebSocket will handle it
                }

                return $result;
            });
        } catch (\Exception $e) {
            Log::error('Binance API Error: ' . $e->getMessage());
            // Return empty array on error
            return [];
        }
    }

    /**
     * Get price for a coin (handles USDT pairs)
     */
    public function getCoinPrice(string $coin): ?float
    {
        $coinUpper = strtoupper($coin);
        
        // USDT is always $1.00
        if ($coinUpper === 'USDT') {
            return 1.0;
        }
        
        $symbol = $coinUpper . 'USDT';
        return $this->getCurrentPrice($symbol);
    }

    /**
     * Get klines (candlestick) data for charting
     * 
     * @param string $symbol Trading pair (e.g., BTCUSDT)
     * @param string $interval Kline interval (1m, 5m, 15m, 1h, 4h, 1d, etc.)
     * @param int $limit Number of klines to return (default 500)
     * @return array|null Array of klines or null on error
     */
    public function getKlines(string $symbol, string $interval = '15m', int $limit = 500, ?int $startTime = null, ?int $endTime = null): ?array
    {
        try {
            // Include startTime and endTime in cache key for historical data
            $cacheKey = "binance_klines_{$symbol}_{$interval}_{$limit}";
            if ($startTime) $cacheKey .= "_s{$startTime}";
            if ($endTime) $cacheKey .= "_e{$endTime}";
            
            return Cache::remember($cacheKey, 30, function () use ($symbol, $interval, $limit, $startTime, $endTime) {
                // Simple direct Binance API call (like Demo-Skraken)
                // Support HTTP proxy if configured (for blocked regions)
                try {
                    $httpClient = Http::timeout(30)->withoutVerifying(); // Increased timeout for production
                    
                    // Use proxy if configured in .env
                    if (env('HTTP_PROXY') || env('HTTPS_PROXY')) {
                        $proxy = env('HTTPS_PROXY') ?: env('HTTP_PROXY');
                        $httpClient = $httpClient->withOptions([
                            'proxy' => $proxy
                        ]);
                    }
                    
                    $params = [
                        'symbol' => $symbol,
                        'interval' => $interval,
                        'limit' => $limit
                    ];
                    
                    // Add startTime and endTime if provided (for historical data)
                    if ($startTime !== null) {
                        $params['startTime'] = $startTime;
                    }
                    if ($endTime !== null) {
                        $params['endTime'] = $endTime;
                    }
                    
                    $response = $httpClient->get(self::BASE_URL . '/klines', $params);

                    if ($response->successful()) {
                        $data = $response->json();
                        
                        // Format klines data
                        $klines = [];
                        foreach ($data as $kline) {
                            $klines[] = [
                                'time' => $kline[0] / 1000, // Convert to seconds
                                'open' => (float) $kline[1],
                                'high' => (float) $kline[2],
                                'low' => (float) $kline[3],
                                'close' => (float) $kline[4],
                                'volume' => (float) $kline[5],
                            ];
                        }
                        
                        return $klines;
                    }
                } catch (\Exception $e) {
                    Log::warning('Binance API unreachable for klines: ' . $e->getMessage());
                }
                
                // DO NOT return mock data - return null instead
                // Frontend should fetch klines directly from Binance API (client-side, bypasses server restrictions)
                Log::warning('Binance API unreachable for klines: ' . $symbol . '. Frontend should fetch directly from Binance API.');
                return null; // Return null so frontend knows to fetch directly
            });
        } catch (\Exception $e) {
            Log::error('Binance Klines API Error: ' . $e->getMessage());
            // DO NOT return mock data - return null instead
            // Frontend should fetch klines directly from Binance API (client-side)
            return null;
        }
    }

    /**
     * Generate mock klines data when Binance API is unreachable
     * Uses deterministic approach for consistent chart data
     * Ensures last data point matches current ticker price
     */
    private function generateMockKlines(string $symbol, string $interval, int $limit): array
    {
        $klines = [];
        $basePrice = $this->getBasePriceForSymbol($symbol);
        $currentTime = time();
        
        // Get current REAL ticker price (from WebSocket or API) to match the last kline
        // This ensures chart matches current market price
        try {
            $currentTicker = $this->getTickerPriceChange($symbol);
            $targetPrice = $currentTicker ? (float) $currentTicker['price'] : $basePrice;
        } catch (\Exception $e) {
            // Fallback to mock ticker if getTickerPriceChange fails
            $currentTicker = $this->generateMockTicker($symbol);
            $targetPrice = (float) $currentTicker['price'];
        }
        
        // Interval in seconds
        $intervalSeconds = [
            '1m' => 60,
            '5m' => 300,
            '15m' => 900,
            '1h' => 3600,
            '4h' => 14400,
            '1d' => 86400,
        ];
        
        $intervalSec = $intervalSeconds[$interval] ?? 900;
        
        // Use same time slot as ticker (5-minute slots) for consistency
        $timeSlot = floor($currentTime / 300); // Match ticker's 5-minute slots
        $seed = crc32($symbol . $timeSlot . $interval);
        mt_srand($seed);
        
        // Start from target price range (historical data should end at target price)
        // Calculate historical start price (target price ±3% range for realistic chart)
        $priceVariation = $targetPrice * 0.03; // 3% variation (realistic 24h range)
        $startPrice = $targetPrice - ($priceVariation * (mt_rand(40, 80) / 100)); // Start 1.2-2.4% below target
        
        // Calculate min/max for realistic chart range (target ±5%)
        $minPrice = $targetPrice * 0.95;
        $maxPrice = $targetPrice * 1.05;
        
        for ($i = $limit - 1; $i >= 0; $i--) {
            $time = $currentTime - ($i * $intervalSec);
            
            // Progressively move from start to target price
            $progress = 1 - ($i / $limit); // 0 to 1
            $basePriceForKline = $startPrice + (($targetPrice - $startPrice) * $progress);
            
            // Add realistic price movement (sine wave + small random walk)
            $trend = sin($i * 0.05) * 0.005; // Long-term trend (smaller for smoother chart)
            $volatility = (mt_rand(-10, 10) / 1000); // Small random movement (reduced)
            $priceChange = $basePriceForKline * ($trend + $volatility);
            $currentPrice = max($minPrice, min($maxPrice, $basePriceForKline + $priceChange)); // Clamp to target ±5%
            
            // For the last kline, ensure it matches target price exactly
            if ($i === 0) {
                $currentPrice = $targetPrice;
            }
            
            // OHLC with realistic relationships (ensure last candle matches ticker)
            $open = $i === 0 ? $targetPrice : ($basePriceForKline * (1 + (mt_rand(-10, 10) / 1000)));
            $close = $i === 0 ? $targetPrice : ($currentPrice * (1 + (mt_rand(-10, 10) / 1000)));
            
            // Ensure OHLC are within realistic range
            $open = max($minPrice, min($maxPrice, $open));
            $close = max($minPrice, min($maxPrice, $close));
            
            // High/Low should be around open/close
            $high = max($open, $close) * (1 + abs(mt_rand(0, 5) / 1000)); // Smaller range
            $low = min($open, $close) * (1 - abs(mt_rand(0, 5) / 1000));  // Smaller range
            
            // Ensure high/low are within range
            $high = min($maxPrice, $high);
            $low = max($minPrice, $low);
            
            // Volume based on price movement (more movement = more volume)
            $priceMovement = abs($close - $open) / max($open, 0.0001);
            $volumeMultiplier = $basePrice > 1000 ? 1000000 : ($basePrice > 100 ? 500000 : 100000);
            $volume = $volumeMultiplier * (1 + $priceMovement * 5) * (0.8 + (mt_rand(0, 40) / 100));
            
            $klines[] = [
                'time' => $time,
                'open' => round($open, 2),
                'high' => round($high, 2),
                'low' => round($low, 2),
                'close' => round($close, 2),
                'volume' => round($volume, 2),
            ];
        }
        
        mt_srand(); // Reset seed
        
        return $klines;
    }

    /**
     * Get base price for symbol (for mock data)
     */
    private function getBasePriceForSymbol(string $symbol): float
    {
        $basePrices = [
            'BTCUSDT' => 90000,
            'ETHUSDT' => 3000,
            'BNBUSDT' => 600,
            'SOLUSDT' => 100,
            'XRPUSDT' => 0.5,
            'ADAUSDT' => 0.5,
            'DOGEUSDT' => 0.1,
            'DOTUSDT' => 7,
            'LTCUSDT' => 100,
            'AVAXUSDT' => 30,
            'TRXUSDT' => 0.1,
            'LINKUSDT' => 15,
        ];
        
        return $basePrices[$symbol] ?? 100;
    }

    /**
     * Get current ticker data for a symbol (price, change, etc.)
     */
    public function getTickerData(string $symbol): ?array
    {
        return $this->getTickerPriceChange($symbol);
    }

}

