<?php
/**
 * QuoteService.php
 * ------------------------------------------------------------
 * Live quote service with production API-credit guard.
 *
 * Rules:
 * - Crypto quotes: Binance public ticker only. No TwelveData credits.
 * - Forex quotes: FMP/TwelveData through LiveQuoteService. Metals use GoldAPI.io/Gold-API/TwelveData/metals providers; FMP is blocked for metals by default.
 */

require_once __DIR__ . '/../config/database.php';
if (file_exists(__DIR__ . '/ApiCreditGuard.php')) {
    require_once __DIR__ . '/ApiCreditGuard.php';
}
if (file_exists(__DIR__ . '/LiveQuoteService.php')) {
    require_once __DIR__ . '/LiveQuoteService.php';
}

class QuoteService
{
    public const BUILD = 'QUOTE_SERVICE_METAL_FMP_ROUTING_FIX_V4_2026_06_04';

    public static function getLivePrice(string $symbol): array
    {
        $symbol = strtoupper(str_replace(['/', '-', '_', ' '], '', trim($symbol)));
        if ($symbol === '') {
            return ['success' => false, 'error' => 'Empty symbol.'];
        }

        /*
         * Platform-wide production quote router.
         * LiveQuoteService handles provider order, market_quotes cache,
         * metals/forex/indices fallbacks and API guard logic.
         */
        if (class_exists('LiveQuoteService')) {
            try {
                $quote = LiveQuoteService::getQuote($symbol);
                if (!empty($quote['success']) && is_numeric($quote['price'] ?? null)) {
                    return $quote;
                }
            } catch (Throwable $e) {
                // Fall through to legacy QuoteService logic.
            }
        }

        if (self::isCryptoSymbol($symbol)) {
            return self::binanceTicker($symbol);
        }

        if (class_exists('ApiCreditGuard') && !ApiCreditGuard::boolSetting('quote_service_paid_quotes_enabled', true)) {
            return [
                'success' => false,
                'provider' => 'guarded_quote',
                'source' => 'paid_quotes_disabled',
                'price' => null,
                'timestamp' => gmdate('Y-m-d H:i:s'),
                'error' => 'Paid quote providers are disabled by API guard.',
            ];
        }

        if (!class_exists('ApiCreditGuard') || ApiCreditGuard::boolSetting('quote_service_twelvedata_enabled', true)) {
            $td = self::twelvedataPrice($symbol);
            if (!empty($td['success'])) {
                return $td;
            }
        }

        if (class_exists('ApiCreditGuard') && ApiCreditGuard::boolSetting('quote_service_fmp_fallback_enabled', true)) {
            if (!self::isMetalSymbol($symbol) || self::fmpMetalsEnabled()) {
                $fmp = self::fmpQuote($symbol);
                if (!empty($fmp['success'])) {
                    return $fmp;
                }
            }
        }

        return [
            'success' => false,
            'provider' => 'guarded_quote',
            'source' => 'no_live_quote_available',
            'price' => null,
            'timestamp' => gmdate('Y-m-d H:i:s'),
            'error' => 'No guarded live quote provider returned a usable price.',
        ];
    }

    public static function isCryptoSymbol(string $symbol): bool
    {
        $symbol = strtoupper(str_replace(['/', '-', '_', ' '], '', trim($symbol)));
        if (class_exists('ApiCreditGuard') && method_exists('ApiCreditGuard', 'isCrypto')) {
            return ApiCreditGuard::isCrypto($symbol);
        }
        return (bool)preg_match('/^(BTC|ETH|SOL|BNB|XRP|ADA|DOGE|AVAX|LINK|LTC|BCH|DOT|MATIC|TRX|ATOM|NEAR|APT|OP|ARB)/', $symbol);
    }

    public static function isMetalSymbol(string $symbol): bool
    {
        $symbol = strtoupper(str_replace(['/', '-', '_', ' '], '', trim($symbol)));
        return in_array(substr($symbol, 0, 3), ['XAU','XAG','XPT','XPD'], true);
    }

    private static function fmpMetalsEnabled(): bool
    {
        if (class_exists('ApiCreditGuard') && method_exists('ApiCreditGuard', 'boolSetting')) {
            return ApiCreditGuard::boolSetting('live_quote_fmp_metals_enabled', false)
                || ApiCreditGuard::boolSetting('quote_service_fmp_metals_enabled', false);
        }
        return false;
    }

    public static function cryptoProviderSymbol(string $symbol): string
    {
        $symbol = strtoupper(str_replace(['/', '-', '_', ' '], '', trim($symbol)));
        if (class_exists('ApiCreditGuard') && method_exists('ApiCreditGuard', 'symbolProviderSymbol')) {
            return ApiCreditGuard::symbolProviderSymbol($symbol, [], 'binance');
        }
        if (preg_match('/USD$/', $symbol)) return preg_replace('/USD$/', 'USDT', $symbol);
        if (!str_ends_with($symbol, 'USDT')) return $symbol . 'USDT';
        return $symbol;
    }

    private static function binanceTicker(string $symbol): array
    {
        $providerSymbol = self::cryptoProviderSymbol($symbol);

        /*
         * Prefer Binance USDT-M futures for trading dashboard display, then
         * fallback to Binance spot and Binance Vision. All are public and do
         * not consume TwelveData credits.
         */
        $endpoints = [
            ['provider' => 'binance_futures', 'url' => 'https://fapi.binance.com/fapi/v1/ticker/price?' . http_build_query(['symbol' => $providerSymbol])],
            ['provider' => 'binance_spot', 'url' => 'https://api.binance.com/api/v3/ticker/price?' . http_build_query(['symbol' => $providerSymbol])],
            ['provider' => 'binance_vision', 'url' => 'https://data-api.binance.vision/api/v3/ticker/price?' . http_build_query(['symbol' => $providerSymbol])],
        ];

        $lastError = '';
        foreach ($endpoints as $endpoint) {
            $response = self::httpJson($endpoint['url'], 7);
            if (empty($response['ok'])) {
                $lastError = $response['error'] ?? 'HTTP failed';
                continue;
            }

            $data = $response['data'];
            if (isset($data['price']) && is_numeric($data['price'])) {
                return [
                    'success' => true,
                    'provider' => $endpoint['provider'],
                    'source' => 'live_ticker',
                    'provider_symbol' => $providerSymbol,
                    'price' => (float)$data['price'],
                    'timestamp' => gmdate('Y-m-d H:i:s'),
                ];
            }

            $lastError = 'Binance returned no numeric price.';
        }

        return [
            'success' => false,
            'provider' => 'binance',
            'source' => 'live_ticker_failed',
            'provider_symbol' => $providerSymbol,
            'price' => null,
            'timestamp' => gmdate('Y-m-d H:i:s'),
            'error' => $lastError ?: 'All Binance ticker endpoints failed.',
        ];
    }

    private static function twelvedataPrice(string $symbol): array
    {
        $rows = [];
        if (class_exists('ApiCreditGuard') && method_exists('ApiCreditGuard', 'providerRows')) {
            $rows = ApiCreditGuard::providerRows('twelvedata', 'quote', 1, true);
        }

        if (!$rows && class_exists('ApiCreditGuard')) {
            $guard = ApiCreditGuard::shouldUseProvider('twelvedata', 'quote', 1);
            if (empty($guard['allowed'])) {
                return ['success' => false, 'provider' => 'twelvedata', 'source' => 'guard_blocked', 'error' => $guard['reason'] ?? 'TwelveData blocked by credit guard.'];
            }
            $rows = [$guard['row'] ?? ['provider' => 'twelvedata', 'api_key' => ApiCreditGuard::getApiKey('twelvedata')]];
        }

        if (!$rows) {
            $rows = [['provider' => 'twelvedata', 'api_key' => '']];
        }

        $providerSymbol = class_exists('ApiCreditGuard') ? ApiCreditGuard::twelvedataSymbol($symbol) : (preg_match('/^[A-Z]{6}$/', $symbol) ? substr($symbol, 0, 3) . '/' . substr($symbol, 3, 3) : $symbol);
        $lastError = 'TwelveData API key missing.';

        foreach ($rows as $row) {
            $apiKey = trim((string)($row['api_key'] ?? ''));
            $rowId = (int)($row['id'] ?? 0);
            if ($apiKey === '') {
                continue;
            }

            if (class_exists('ApiCreditGuard') && method_exists('ApiCreditGuard', 'shouldUseProviderRow') && $rowId > 0) {
                $guard = ApiCreditGuard::shouldUseProviderRow($row, 'quote', 1);
                if (empty($guard['allowed'])) {
                    $lastError = $guard['reason'] ?? 'TwelveData row blocked.';
                    continue;
                }
                ApiCreditGuard::recordUsageForRow($row, 1);
            } elseif (class_exists('ApiCreditGuard')) {
                ApiCreditGuard::recordUsage('twelvedata', 1);
            }

            $url = 'https://api.twelvedata.com/price?' . http_build_query([
                'symbol' => $providerSymbol,
                'apikey' => $apiKey,
                'format' => 'JSON',
            ]);

            $response = self::httpJson($url, 10);

            if (empty($response['ok'])) {
                $lastError = $response['error'] ?? 'TwelveData response failed.';
                if (class_exists('ApiCreditGuard') && method_exists('ApiCreditGuard', 'recordFailureForRow') && $rowId > 0) ApiCreditGuard::recordFailureForRow($row, $lastError);
                continue;
            }

            $data = $response['data'];
            if (isset($data['code']) || (isset($data['status']) && strtolower((string)$data['status']) === 'error')) {
                $lastError = (string)($data['message'] ?? 'TwelveData API error.');
                if (class_exists('ApiCreditGuard') && method_exists('ApiCreditGuard', 'recordFailureForRow') && $rowId > 0) ApiCreditGuard::recordFailureForRow($row, $lastError);
                continue;
            }

            if (!isset($data['price']) || !is_numeric($data['price'])) {
                $lastError = 'No numeric price in quote response.';
                if (class_exists('ApiCreditGuard') && method_exists('ApiCreditGuard', 'recordFailureForRow') && $rowId > 0) ApiCreditGuard::recordFailureForRow($row, $lastError);
                continue;
            }

            if (class_exists('ApiCreditGuard') && method_exists('ApiCreditGuard', 'recordSuccessForRow') && $rowId > 0) ApiCreditGuard::recordSuccessForRow($row);
            return [
                'success' => true,
                'provider' => 'twelvedata',
                'source' => 'live_quote',
                'provider_symbol' => $providerSymbol,
                'api_setting_id' => $rowId,
                'price' => (float)$data['price'],
                'timestamp' => gmdate('Y-m-d H:i:s'),
            ];
        }

        return ['success' => false, 'provider' => 'twelvedata', 'source' => 'live_quote', 'error' => $lastError];
    }

    private static function fmpQuote(string $symbol): array
    {
        if (self::isMetalSymbol($symbol) && !self::fmpMetalsEnabled()) {
            return ['success' => false, 'provider' => 'fmp', 'source' => 'metal_fmp_blocked', 'error' => 'FMP metals/commodity quotes are disabled by default. Use GoldAPI.io, Gold-API public, TwelveData, Metals-API, MetalpriceAPI or Alpha Vantage for XAUUSD/XAGUSD.'];
        }

        if (!class_exists('ApiCreditGuard')) return ['success' => false, 'provider' => 'fmp', 'error' => 'ApiCreditGuard unavailable.'];

        $rows = method_exists('ApiCreditGuard', 'providerRows') ? ApiCreditGuard::providerRows('fmp', 'quote', 1, true) : [];
        if (!$rows) {
            $guard = ApiCreditGuard::shouldUseProvider('fmp', 'quote', 1);
            if (empty($guard['allowed'])) {
                return ['success' => false, 'provider' => 'fmp', 'source' => 'guard_blocked', 'error' => $guard['reason'] ?? 'FMP blocked by credit guard.'];
            }
            $rows = [$guard['row'] ?? ['provider' => 'fmp', 'api_key' => ApiCreditGuard::getApiKey('fmp')]];
        }

        $lastError = 'FMP API key missing.';
        foreach ($rows as $row) {
            $apiKey = trim((string)($row['api_key'] ?? ''));
            $rowId = (int)($row['id'] ?? 0);
            if ($apiKey === '') {
                continue;
            }

            if (method_exists('ApiCreditGuard', 'shouldUseProviderRow') && $rowId > 0) {
                $guard = ApiCreditGuard::shouldUseProviderRow($row, 'quote', 1);
                if (empty($guard['allowed'])) {
                    $lastError = $guard['reason'] ?? 'FMP row blocked.';
                    continue;
                }
                ApiCreditGuard::recordUsageForRow($row, 1, 'fmp_stable_quote', $symbol);
            } else {
                ApiCreditGuard::recordUsage('fmp', 1, 'fmp_stable_quote', $symbol);
            }

            /*
             * Use FMP stable quote only. For EURUSD and other forex pairs, FMP's
             * current documentation points to /stable/quote?symbol=EURUSD. The
             * old /api/v3 routes are legacy, and quote-short is a stock short quote
             * endpoint, so neither is used here for production live FX quotes.
             */
            $url = 'https://financialmodelingprep.com/stable/quote?' . http_build_query(['symbol' => $symbol, 'apikey' => $apiKey]);
            $response = self::httpJson($url, 10);

            if (empty($response['ok'])) {
                $lastError = $response['error'] ?? 'FMP stable quote response failed.';
                if (method_exists('ApiCreditGuard', 'recordFailureForRow') && $rowId > 0) ApiCreditGuard::recordFailureForRow($row, $lastError, 'fmp_stable_quote', $symbol);
                continue;
            }

            $data = $response['data'];
            if (isset($data['message'])) {
                $lastError = (string)$data['message'];
                if (stripos($lastError, 'limit') !== false || stripos($lastError, 'upgrade') !== false) {
                    if (method_exists('ApiCreditGuard', 'recordRateLimitedForRow') && $rowId > 0) ApiCreditGuard::recordRateLimitedForRow($row, $lastError, 60, 'fmp_stable_quote', $symbol);
                    continue;
                }
                if (method_exists('ApiCreditGuard', 'recordFailureForRow') && $rowId > 0) ApiCreditGuard::recordFailureForRow($row, $lastError, 'fmp_stable_quote', $symbol);
                continue;
            }

            $rowData = is_array($data) && isset($data[0]) && is_array($data[0]) ? $data[0] : $data;
            $price = $rowData['price'] ?? $rowData['bid'] ?? $rowData['ask'] ?? $rowData['c'] ?? null;
            if (!is_numeric($price)) {
                $lastError = 'No numeric price in FMP stable quote response.';
                if (method_exists('ApiCreditGuard', 'recordFailureForRow') && $rowId > 0) ApiCreditGuard::recordFailureForRow($row, $lastError, 'fmp_stable_quote', $symbol);
                continue;
            }

            if (method_exists('ApiCreditGuard', 'recordSuccessForRow') && $rowId > 0) ApiCreditGuard::recordSuccessForRow($row, 'fmp_stable_quote', $symbol);
            return [
                'success' => true,
                'provider' => 'fmp',
                'source' => 'fmp_stable_quote',
                'provider_symbol' => $symbol,
                'api_setting_id' => $rowId,
                'price' => (float)$price,
                'timestamp' => gmdate('Y-m-d H:i:s'),
            ];
        }

        return ['success' => false, 'provider' => 'fmp', 'source' => 'fmp_stable_quote', 'error' => $lastError];
    }

    private static function httpJson(string $url, int $timeout = 8): array
    {
        if (class_exists('ApiCreditGuard') && method_exists('ApiCreditGuard', 'httpJson')) {
            return ApiCreditGuard::httpJson($url, $timeout);
        }

        $context = stream_context_create([
            'http' => [
                'timeout' => $timeout,
                'ignore_errors' => true,
                'header' => "User-Agent: InstitutionalTradingAssistant/1.0\r\n",
            ]
        ]);
        $raw = @file_get_contents($url, false, $context);
        if ($raw === false || trim($raw) === '') {
            return ['ok' => false, 'error' => 'Empty HTTP response.'];
        }
        $json = json_decode($raw, true);
        if (!is_array($json)) {
            return ['ok' => false, 'error' => 'Invalid JSON response.'];
        }
        return ['ok' => true, 'data' => $json];
    }
}
