<?php

require_once __DIR__ . '/../config/database.php';
require_once __DIR__ . '/EconomicCalendarService.php';

class NewsRiskService
{
    /*
    |--------------------------------------------------------------------------
    | NewsRiskService V2 - DB-first Advisory Engine
    |--------------------------------------------------------------------------
    | This service does NOT hard-block trading.
    | It reads from news_events populated by cron/fetch_news_events.php.
    | It returns risk/advisory data so dashboard/signal engine can:
    | - show news warnings
    | - reduce confidence
    | - mark reduced-risk/caution mode
    | - respect user news preferences
    |
    | Trading setup generation should still continue.
    |--------------------------------------------------------------------------
    */

    public static function check(string $symbol, int $minutesAhead = 90, ?int $userId = null): array
    {
        $symbol = strtoupper(str_replace('/', '', trim($symbol)));
        $preferences = self::userPreferences($userId);

        $minutesBehind = (int)($preferences['news_block_minutes_after'] ?? 30);
        $minutesAhead = max($minutesAhead, (int)($preferences['news_block_minutes_before'] ?? 60));

        $events = self::eventsForSymbol($symbol, $minutesAhead, $minutesBehind);

        if (empty($events)) {
            return [
                'has_news' => false,
                'risk' => 'LOW',
                'advisory_level' => 'LOW',
                'execution_mode' => 'NORMAL',
                'score_penalty' => 0,
                'confidence_adjustment' => 0,
                'trade_allowed' => true,
                'user_allows_news_trading' => true,
                'title' => '',
                'time' => '',
                'message' => 'No relevant medium/high-impact economic news detected near the current trading window.',
                'events' => [],
                'source' => 'news_events_db',
            ];
        }

        $weightedEvents = [];

        foreach ($events as $event) {
            $event['weight'] = self::eventWeight($event, $symbol);
            $event['window_phase'] = self::windowPhase((int)($event['timestamp'] ?? 0));
            $event['advisory'] = self::eventAdvisory($event, $symbol);
            $weightedEvents[] = $event;
        }

        usort($weightedEvents, function ($a, $b) {
            if (($b['weight'] ?? 0) !== ($a['weight'] ?? 0)) {
                return ($b['weight'] ?? 0) <=> ($a['weight'] ?? 0);
            }

            return abs((int)($a['seconds_until'] ?? 0)) <=> abs((int)($b['seconds_until'] ?? 0));
        });

        $primary = $weightedEvents[0];
        $highestRisk = self::highestRisk($weightedEvents);
        $confidenceAdjustment = self::confidenceAdjustment($weightedEvents, $symbol);
        $executionMode = self::executionMode($highestRisk, $confidenceAdjustment, $primary);
        $tradeAllowed = self::tradeAllowedByPreference($weightedEvents, $preferences);

        return [
            'has_news' => true,
            'risk' => $highestRisk,
            'advisory_level' => $highestRisk,
            'execution_mode' => $executionMode,
            'score_penalty' => $confidenceAdjustment,
            'confidence_adjustment' => $confidenceAdjustment,
            'trade_allowed' => $tradeAllowed,
            'user_allows_news_trading' => $tradeAllowed,
            'title' => $primary['title'] ?? '',
            'time' => $primary['time'] ?? '',
            'message' => self::buildMessage($primary, $highestRisk, $confidenceAdjustment, $tradeAllowed),
            'events' => array_slice($weightedEvents, 0, 10),
            'source' => 'news_events_db',
        ];
    }

    public static function headlineRisk(string $symbol): array
    {
        // Backward compatibility for old dashboard calls.
        return self::check($symbol, 180);
    }

    private static function eventsForSymbol(string $symbol, int $minutesAhead = 90, int $minutesBehind = 30): array
    {
        try {
            $currencies = self::currenciesForSymbol($symbol);

            $now = time();
            $start = $now - ($minutesBehind * 60);
            $end = $now + ($minutesAhead * 60);

            $currencyPlaceholders = implode(',', array_fill(0, count($currencies), '?'));

            $sql = "
                SELECT *
                FROM news_events
                WHERE event_timestamp BETWEEN ? AND ?
                  AND impact IN ('MEDIUM','HIGH','CRITICAL')
                  AND (
                        currency IN ($currencyPlaceholders)
                        OR title LIKE '%FED%'
                        OR title LIKE '%FOMC%'
                        OR title LIKE '%POWELL%'
                        OR title LIKE '%CPI%'
                        OR title LIKE '%PCE%'
                        OR title LIKE '%NFP%'
                        OR title LIKE '%NONFARM%'
                        OR title LIKE '%INFLATION%'
                        OR title LIKE '%INTEREST RATE%'
                        OR title LIKE '%RATE DECISION%'
                        OR title LIKE '%JOBLESS%'
                        OR title LIKE '%UNEMPLOYMENT%'
                        OR title LIKE '%GDP%'
                        OR title LIKE '%PMI%'
                        OR title LIKE '%RETAIL SALES%'
                        OR title LIKE '%SPEECH%'
                        OR title LIKE '%SPEAKS%'
                        OR title LIKE '%TESTIFIES%'
                  )
                ORDER BY event_timestamp ASC
                LIMIT 80
            ";

            $params = [$start, $end];

            foreach ($currencies as $currency) {
                $params[] = $currency;
            }

            $stmt = db()->prepare($sql);
            $stmt->execute($params);

            $rows = $stmt->fetchAll(PDO::FETCH_ASSOC) ?: [];
            $events = [];

            foreach ($rows as $row) {
                if (!self::eventAffectsSymbol($row, $symbol)) {
                    continue;
                }

                $timestamp = (int)($row['event_timestamp'] ?? 0);
                $secondsUntil = $timestamp - time();

                $events[] = [
                    'id' => $row['id'] ?? null,
                    'title' => $row['title'] ?? 'Economic event',
                    'event' => $row['title'] ?? 'Economic event',
                    'currency' => strtoupper((string)($row['currency'] ?? '')),
                    'impact' => strtoupper((string)($row['impact'] ?? 'LOW')),
                    'time' => $timestamp ? date('Y-m-d H:i', $timestamp) : (string)($row['event_time'] ?? ''),
                    'timestamp' => $timestamp,
                    'seconds_until' => $secondsUntil,
                    'minutes_until' => (int)round($secondsUntil / 60),
                    'countdown_label' => self::countdownLabel($secondsUntil),
                    'actual' => $row['actual'] ?? null,
                    'forecast' => $row['forecast'] ?? null,
                    'previous' => $row['previous'] ?? null,
                    'source' => $row['source'] ?? 'news_events',
                ];
            }

            return $events;
        } catch (Throwable $e) {
            self::log('eventsForSymbol failed: ' . $e->getMessage());
            return [];
        }
    }

    private static function userPreferences(?int $userId): array
    {
        $defaults = [
            'avoid_news_trades' => 0,
            'trade_high_impact_news' => 1,
            'trade_medium_impact_news' => 1,
            'news_block_minutes_before' => 60,
            'news_block_minutes_after' => 30,
        ];

        if (!$userId) {
            return $defaults;
        }

        try {
            $stmt = db()->prepare("SHOW COLUMNS FROM users");
            $stmt->execute();
            $columns = array_map('strtolower', $stmt->fetchAll(PDO::FETCH_COLUMN) ?: []);

            $select = ['id'];

            foreach (array_keys($defaults) as $column) {
                if (in_array(strtolower($column), $columns, true)) {
                    $select[] = $column;
                }
            }

            $sql = "SELECT " . implode(',', $select) . " FROM users WHERE id = ? LIMIT 1";
            $userStmt = db()->prepare($sql);
            $userStmt->execute([$userId]);
            $row = $userStmt->fetch(PDO::FETCH_ASSOC) ?: [];

            return array_merge($defaults, $row);
        } catch (Throwable $e) {
            self::log('userPreferences failed: ' . $e->getMessage());
            return $defaults;
        }
    }

    private static function tradeAllowedByPreference(array $events, array $preferences): bool
    {
        // If avoid_news_trades is off, trading is allowed but warnings still show.
        if ((int)($preferences['avoid_news_trades'] ?? 0) !== 1) {
            return true;
        }

        foreach ($events as $event) {
            $impact = strtoupper((string)($event['impact'] ?? 'LOW'));

            if (in_array($impact, ['HIGH', 'CRITICAL'], true) && (int)($preferences['trade_high_impact_news'] ?? 1) !== 1) {
                return false;
            }

            if ($impact === 'MEDIUM' && (int)($preferences['trade_medium_impact_news'] ?? 1) !== 1) {
                return false;
            }
        }

        return true;
    }

    private static function eventWeight(array $event, string $symbol): int
    {
        $impact = strtoupper((string)($event['impact'] ?? 'LOW'));
        $title = strtoupper((string)($event['title'] ?? ''));
        $currency = strtoupper((string)($event['currency'] ?? ''));

        $weight = match ($impact) {
            'CRITICAL' => 100,
            'HIGH' => 80,
            'MEDIUM' => 45,
            default => 10,
        };

        if (self::isSpeech($title)) $weight += 12;
        if (self::isCentralBank($title)) $weight += 15;
        if (self::isInflationOrJobs($title)) $weight += 18;
        if ($symbol === 'XAUUSD' && $currency === 'USD') $weight += 25;

        if ($symbol === 'XAUUSD' && preg_match('/\b(FED|FOMC|POWELL|CPI|PCE|NFP|NONFARM|INFLATION|DOLLAR|YIELD|RATE)\b/i', $title)) {
            $weight += 25;
        }

        $minutes = abs((int)($event['minutes_until'] ?? 999));

        if ($minutes <= 15) $weight += 25;
        elseif ($minutes <= 30) $weight += 15;
        elseif ($minutes <= 60) $weight += 8;

        return min(150, $weight);
    }

    private static function confidenceAdjustment(array $events, string $symbol): int
    {
        $maxPenalty = 0;

        foreach ($events as $event) {
            $impact = strtoupper((string)($event['impact'] ?? 'LOW'));
            $title = strtoupper((string)($event['title'] ?? ''));
            $minutesAbs = abs((int)($event['minutes_until'] ?? 999));

            $penalty = match ($impact) {
                'CRITICAL' => -15,
                'HIGH' => -10,
                'MEDIUM' => -4,
                default => 0,
            };

            if (self::isSpeech($title)) $penalty -= 3;
            if (self::isInflationOrJobs($title)) $penalty -= 4;

            if ($symbol === 'XAUUSD' && preg_match('/\b(FED|FOMC|POWELL|CPI|PCE|NFP|NONFARM|INFLATION|DOLLAR|YIELD|RATE)\b/i', $title)) {
                $penalty -= 5;
            }

            if ($minutesAbs <= 15) $penalty -= 5;
            elseif ($minutesAbs <= 30) $penalty -= 3;

            $maxPenalty = min($maxPenalty, $penalty);
        }

        return max(-25, $maxPenalty);
    }

    private static function highestRisk(array $events): string
    {
        $rank = ['LOW' => 1, 'MEDIUM' => 2, 'HIGH' => 3, 'EXTREME' => 4];
        $highest = 'LOW';

        foreach ($events as $event) {
            $impact = strtoupper((string)($event['impact'] ?? 'LOW'));
            $risk = $impact === 'CRITICAL' ? 'EXTREME' : $impact;

            if (($rank[$risk] ?? 0) > ($rank[$highest] ?? 0)) {
                $highest = $risk;
            }
        }

        return $highest;
    }

    private static function executionMode(string $risk, int $confidenceAdjustment, array $primary): string
    {
        $phase = strtoupper((string)($primary['window_phase'] ?? ''));

        if ($risk === 'EXTREME' || $confidenceAdjustment <= -18) {
            return $phase === 'POST_NEWS' ? 'POST_NEWS_VOLATILITY' : 'NEWS_HIGH_CAUTION';
        }

        if ($risk === 'HIGH' || $confidenceAdjustment <= -10) {
            return 'NEWS_CAUTION';
        }

        if ($risk === 'MEDIUM' || $confidenceAdjustment < 0) {
            return 'NEWS_REDUCED_RISK';
        }

        return 'NORMAL';
    }

    private static function buildMessage(array $primary, string $risk, int $confidenceAdjustment, bool $tradeAllowed): string
    {
        $impact = strtoupper((string)($primary['impact'] ?? $risk));
        $currency = strtoupper((string)($primary['currency'] ?? ''));
        $title = trim((string)($primary['title'] ?? 'Economic event'));
        $countdown = trim((string)($primary['countdown_label'] ?? ''));

        $parts = [];
        $parts[] = "{$impact} {$currency} news: {$title}" . ($countdown ? " ({$countdown})" : '') . ".";

        if ($confidenceAdjustment < 0) {
            $parts[] = "Suggested confidence adjustment: {$confidenceAdjustment} points.";
        }

        if (!$tradeAllowed) {
            $parts[] = "Your settings do not allow trading this news window, but the setup can still be generated for review.";
        } else {
            $parts[] = "Trading setup generation remains enabled. Use reduced risk, confirmation, and spread/slippage caution.";
        }

        if (($primary['window_phase'] ?? '') === 'POST_NEWS') {
            $parts[] = "Post-news volatility may still be active.";
        }

        return implode(' ', $parts);
    }

    private static function eventAdvisory(array $event, string $symbol): string
    {
        $impact = strtoupper((string)($event['impact'] ?? 'LOW'));
        $title = strtoupper((string)($event['title'] ?? ''));
        $currency = strtoupper((string)($event['currency'] ?? ''));

        if ($symbol === 'XAUUSD' && $currency === 'USD') {
            return "{$impact} USD event can strongly affect XAUUSD through dollar, yields, and liquidity volatility.";
        }

        if (self::isSpeech($title)) {
            return "{$impact} central-bank speech risk. Watch for unscheduled volatility and directional repricing.";
        }

        if (self::isInflationOrJobs($title)) {
            return "{$impact} inflation/jobs event. Expect spread widening, slippage, and fast liquidity sweeps.";
        }

        return "{$impact} economic event. Use news-aware risk management.";
    }

    private static function windowPhase(int $timestamp): string
    {
        $diff = $timestamp - time();

        if ($diff > 0) {
            if ($diff <= 15 * 60) return 'PRE_NEWS_IMMEDIATE';
            if ($diff <= 60 * 60) return 'PRE_NEWS';
            return 'UPCOMING';
        }

        if (abs($diff) <= 30 * 60) return 'POST_NEWS';

        return 'RECENT_NEWS';
    }

    private static function eventAffectsSymbol(array $event, string $symbol): bool
    {
        $symbol = strtoupper(str_replace('/', '', $symbol));
        $currencies = self::currenciesForSymbol($symbol);

        $currency = strtoupper((string)($event['currency'] ?? ''));
        $title = strtoupper((string)($event['title'] ?? $event['event'] ?? ''));

        if ($symbol === 'XAUUSD') {
            return $currency === 'USD'
                || str_contains($title, 'USD')
                || str_contains($title, 'UNITED STATES')
                || str_contains($title, 'FED')
                || str_contains($title, 'FOMC')
                || str_contains($title, 'POWELL')
                || str_contains($title, 'CPI')
                || str_contains($title, 'PCE')
                || str_contains($title, 'NFP')
                || str_contains($title, 'NONFARM')
                || str_contains($title, 'INFLATION')
                || str_contains($title, 'DOLLAR')
                || str_contains($title, 'YIELD')
                || str_contains($title, 'INTEREST RATE');
        }

        foreach ($currencies as $cur) {
            if ($currency === $cur || str_contains($title, $cur)) {
                return true;
            }
        }

        return false;
    }

    private static function currenciesForSymbol(string $symbol): array
    {
        $symbol = strtoupper(str_replace('/', '', trim($symbol)));

        if ($symbol === 'XAUUSD') {
            return ['USD', 'XAU'];
        }

        if (strlen($symbol) >= 6) {
            return [substr($symbol, 0, 3), substr($symbol, 3, 3)];
        }

        return ['USD'];
    }

    private static function countdownLabel(int $seconds): string
    {
        if ($seconds === 0) return 'live now';

        if ($seconds < 0) {
            $minutesAgo = (int)floor(abs($seconds) / 60);

            if ($minutesAgo < 60) return $minutesAgo . ' minutes ago';

            return intdiv($minutesAgo, 60) . 'h ' . ($minutesAgo % 60) . 'm ago';
        }

        $minutes = (int)floor($seconds / 60);

        if ($minutes < 60) return 'in ' . $minutes . ' minutes';

        return 'in ' . intdiv($minutes, 60) . 'h ' . ($minutes % 60) . 'm';
    }

    private static function isSpeech(string $title): bool
    {
        return (bool)preg_match('/\b(SPEECH|SPEAKS|TESTIFIES|TESTIMONY|REMARKS|PRESS CONFERENCE)\b/i', $title);
    }

    private static function isCentralBank(string $title): bool
    {
        return (bool)preg_match('/\b(FED|FOMC|POWELL|ECB|BOE|BOJ|RBA|BOC|RBNZ|SNB|CENTRAL BANK|MONETARY POLICY|RATE DECISION|INTEREST RATE)\b/i', $title);
    }

    private static function isInflationOrJobs(string $title): bool
    {
        return (bool)preg_match('/\b(CPI|PCE|INFLATION|NFP|NONFARM|UNEMPLOYMENT|JOBLESS|CLAIMS|PAYROLLS|WAGES)\b/i', $title);
    }

    private static function log(string $message): void
    {
        try {
            $dir = __DIR__ . '/../storage/logs';

            if (!is_dir($dir)) {
                @mkdir($dir, 0755, true);
            }

            $line = '[' . date('Y-m-d H:i:s') . '] ' . $message . PHP_EOL;

            @file_put_contents($dir . '/news_risk.log', $line, FILE_APPEND);
        } catch (Throwable $e) {
            // logging should never break analysis
        }
    }
}
