"""
Geopolitical pattern engine.
Scores current events against historical templates and generates trade signals.
"""
from datetime import datetime, timedelta
from typing import List, Dict, Any, Optional
import json


# ── Pattern Taxonomy Tree ─────────────────────────────────────────────────────
PATTERN_TAXONOMY = {
    "id": "root",
    "label": "Pattern Library",
    "children": [
        {
            "id": "geopolitical",
            "label": "Geopolitical",
            "children": [
                {
                    "id": "armed_conflict",
                    "label": "Armed Conflict",
                    "children": [
                        {"id": "armed_conflict.middle_east", "label": "Middle East"},
                        {"id": "armed_conflict.europe", "label": "Europe / Ukraine"},
                        {"id": "armed_conflict.asia_pacific", "label": "Asia-Pacific"},
                    ],
                },
                {
                    "id": "sanctions",
                    "label": "Sanctions & Trade War",
                    "children": [
                        {"id": "sanctions.us_china", "label": "US–China"},
                        {"id": "sanctions.russia", "label": "Russia"},
                        {"id": "sanctions.iran", "label": "Iran"},
                    ],
                },
                {
                    "id": "territorial",
                    "label": "Territorial Disputes",
                    "children": [
                        {"id": "territorial.taiwan", "label": "Taiwan Strait"},
                        {"id": "territorial.south_china_sea", "label": "South China Sea"},
                    ],
                },
                {
                    "id": "elections",
                    "label": "Elections & Political Risk",
                    "children": [
                        {"id": "elections.us", "label": "US Elections"},
                        {"id": "elections.europe", "label": "Europe"},
                        {"id": "elections.emerging", "label": "Emerging Markets"},
                    ],
                },
            ],
        },
        {
            "id": "monetary_policy",
            "label": "Monetary Policy",
            "children": [
                {"id": "monetary_policy.fed", "label": "Federal Reserve"},
                {"id": "monetary_policy.ecb", "label": "European Central Bank"},
                {"id": "monetary_policy.boj", "label": "Bank of Japan"},
                {"id": "monetary_policy.pivot", "label": "Policy Pivot / Surprise"},
                {"id": "monetary_policy.divergence", "label": "Central Bank Divergence"},
            ],
        },
        {
            "id": "economic",
            "label": "Economic Shocks",
            "children": [
                {"id": "economic.banking_crisis", "label": "Banking Crisis"},
                {"id": "economic.recession", "label": "Recession Fears"},
                {"id": "economic.inflation", "label": "Inflation Surge"},
                {"id": "economic.china_slowdown", "label": "China Slowdown"},
                {"id": "economic.debt_ceiling", "label": "US Debt Ceiling"},
            ],
        },
        {
            "id": "commodity",
            "label": "Commodity Shocks",
            "children": [
                {
                    "id": "commodity.energy",
                    "label": "Energy",
                    "children": [
                        {"id": "commodity.energy.crude", "label": "Crude Oil (OPEC / supply)"},
                        {"id": "commodity.energy.gas", "label": "Natural Gas"},
                    ],
                },
                {
                    "id": "commodity.agriculture",
                    "label": "Agriculture",
                    "children": [
                        {"id": "commodity.agriculture.wheat", "label": "Wheat / Grains"},
                        {"id": "commodity.agriculture.softs", "label": "Softs (Coffee, Sugar)"},
                    ],
                },
                {
                    "id": "commodity.metals",
                    "label": "Metals",
                    "children": [
                        {"id": "commodity.metals.gold", "label": "Gold (Safe Haven)"},
                        {"id": "commodity.metals.copper", "label": "Copper (China / Growth)"},
                    ],
                },
            ],
        },
        {
            "id": "risk_off",
            "label": "Risk-Off Events",
            "children": [
                {"id": "risk_off.pandemic", "label": "Pandemic / Health Crisis"},
                {"id": "risk_off.financial_contagion", "label": "Financial Contagion"},
                {"id": "risk_off.natural_disaster", "label": "Natural Disaster"},
                {"id": "risk_off.nuclear", "label": "Nuclear Risk"},
            ],
        },
        {
            "id": "market_structure",
            "label": "Market Structure",
            "children": [
                {"id": "market_structure.volatility", "label": "Volatility Regime (VIX)"},
                {"id": "market_structure.positioning", "label": "Positioning Extremes"},
                {"id": "market_structure.sentiment", "label": "Sentiment Extremes"},
                {"id": "market_structure.liquidity", "label": "Liquidity Crisis"},
            ],
        },
    ],
}


# ── Historical geopolitical pattern library ───────────────────────────────────
GEO_PATTERNS = [
    {
        "id": "P001",
        "name": "Middle East Military Escalation → Oil Spike",
        "description": "Armed conflict or threat in Gulf region triggers Brent/WTI crude spike +10-20% within 2-4 weeks",
        "triggers": ["military", "energy", "sanctions"],
        "keywords": ["Iran", "Israel", "Saudi", "Gulf", "Strait of Hormuz", "OPEC"],
        "taxonomy_path": ["geopolitical", "armed_conflict", "armed_conflict.middle_east"],
        "historical_instances": [
            {"date": "2019-09-14", "event": "Attack on Saudi Aramco facilities", "brent_move": +14.6, "days": 2},
            {"date": "2020-01-03", "event": "Soleimani assassination", "brent_move": +4.4, "days": 1},
            {"date": "2022-02-24", "event": "Russia invades Ukraine", "brent_move": +28.0, "days": 10},
        ],
        "suggested_trades": [
            {"strategy": "Bull Call Spread", "underlying": "USO", "rationale": "Oil ETF call spread, limited risk"},
            {"strategy": "Long Call", "underlying": "CL=F", "rationale": "WTI crude direct exposure"},
        ],
        "asset_class": "energy",
        "expected_move_pct": 12.0,
        "probability": 0.65,
        "horizon_days": 30,
    },
    {
        "id": "P002",
        "name": "US Tariff Announcement → Agriculture Selloff",
        "description": "Trump/US tariff threats on China cause immediate selloff in soy, corn, wheat (retaliatory risk)",
        "triggers": ["trade_war", "political_speech"],
        "keywords": ["tariff", "China", "trade", "soybean", "agriculture", "import duty"],
        "taxonomy_path": ["geopolitical", "sanctions", "sanctions.us_china"],
        "historical_instances": [
            {"date": "2018-07-06", "event": "US-China trade war tariffs", "zs_move": -10.2, "days": 30},
            {"date": "2019-05-10", "event": "Trump tariff escalation tweet", "zs_move": -5.8, "days": 5},
            {"date": "2025-02-01", "event": "Trump 25% tariff on Canada/Mexico", "zw_move": -3.4, "days": 3},
        ],
        "suggested_trades": [
            {"strategy": "Bear Put Spread", "underlying": "SOYB", "rationale": "Downside hedge on soy ETF"},
            {"strategy": "Long Put", "underlying": "ZS=F", "rationale": "Soybean futures put"},
        ],
        "asset_class": "agriculture",
        "expected_move_pct": -8.0,
        "probability": 0.70,
        "horizon_days": 21,
    },
    {
        "id": "P003",
        "name": "Geopolitical Risk Flight → Gold Rally",
        "description": "Major geopolitical uncertainty drives safe-haven demand for gold +5-15%",
        "triggers": ["military", "health_crisis", "financial_crisis", "elections"],
        "keywords": ["nuclear", "war", "crisis", "uncertainty", "safe haven", "debt ceiling"],
        "taxonomy_path": ["commodity", "commodity.metals", "commodity.metals.gold"],
        "historical_instances": [
            {"date": "2022-02-24", "event": "Ukraine invasion", "gc_move": +6.8, "days": 14},
            {"date": "2023-10-07", "event": "Hamas attack on Israel", "gc_move": +9.2, "days": 30},
            {"date": "2020-03-01", "event": "COVID-19 fear peak", "gc_move": +12.1, "days": 45},
        ],
        "suggested_trades": [
            {"strategy": "Long Call", "underlying": "GLD", "rationale": "Gold ETF call for safe-haven rally"},
            {"strategy": "Bull Call Spread", "underlying": "GC=F", "rationale": "Gold futures spread, capped risk"},
        ],
        "asset_class": "metals",
        "expected_move_pct": 7.5,
        "probability": 0.72,
        "horizon_days": 30,
    },
    {
        "id": "P004",
        "name": "Fed Hawkish Pivot → Dollar Surge / EM Currency Crash",
        "description": "Fed signals higher-for-longer rates → USD Index rallies, EUR/USD drops",
        "triggers": ["political_speech"],
        "keywords": ["Fed", "interest rate", "hike", "hawkish", "inflation", "FOMC", "Powell"],
        "taxonomy_path": ["monetary_policy", "monetary_policy.fed"],
        "historical_instances": [
            {"date": "2022-06-15", "event": "Fed 75bps hike", "dxy_move": +3.2, "days": 5},
            {"date": "2023-03-22", "event": "Fed signals further hikes", "eurusd_move": -1.8, "days": 7},
        ],
        "suggested_trades": [
            {"strategy": "Bear Put Spread", "underlying": "FXE", "rationale": "EUR/USD put spread"},
            {"strategy": "Long Call", "underlying": "UUP", "rationale": "Dollar index ETF call"},
        ],
        "asset_class": "forex",
        "expected_move_pct": 3.0,
        "probability": 0.68,
        "horizon_days": 14,
    },
    {
        "id": "P005",
        "name": "China Economic Slowdown → Copper/Metals Selloff",
        "description": "Weak Chinese PMI or stimulus disappointment drives copper lower (China = 50%+ of global demand)",
        "triggers": ["resource_scarcity", "trade_war"],
        "keywords": ["China", "PMI", "slowdown", "recession", "property", "Evergrande", "copper demand"],
        "taxonomy_path": ["economic", "economic.china_slowdown"],
        "historical_instances": [
            {"date": "2015-08-24", "event": "China Black Monday", "hg_move": -8.4, "days": 5},
            {"date": "2022-11-01", "event": "China PMI contraction", "hg_move": -5.2, "days": 10},
        ],
        "suggested_trades": [
            {"strategy": "Long Put", "underlying": "COPX", "rationale": "Copper miners ETF put"},
            {"strategy": "Bear Put Spread", "underlying": "HG=F", "rationale": "Copper futures spread"},
        ],
        "asset_class": "metals",
        "expected_move_pct": -6.5,
        "probability": 0.60,
        "horizon_days": 21,
    },
    {
        "id": "P006",
        "name": "Ukraine/Russia War Escalation → Wheat Spike + Defense Rally",
        "description": "New escalation in Russia-Ukraine conflict → wheat/fertilizer spike, defense stocks rally",
        "triggers": ["military", "resource_scarcity"],
        "keywords": ["Russia", "Ukraine", "Zelensky", "Kyiv", "grain corridor", "Black Sea", "NATO"],
        "taxonomy_path": ["geopolitical", "armed_conflict", "armed_conflict.europe"],
        "historical_instances": [
            {"date": "2022-02-24", "event": "Full-scale invasion", "zw_move": +50.0, "days": 45},
            {"date": "2022-07-22", "event": "Grain deal collapse threat", "zw_move": +6.3, "days": 3},
            {"date": "2023-07-17", "event": "Russia exits grain deal", "zw_move": +8.5, "days": 2},
        ],
        "suggested_trades": [
            {"strategy": "Long Call", "underlying": "WEAT", "rationale": "Wheat ETF call on supply shock"},
            {"strategy": "Bull Call Spread", "underlying": "LMT", "rationale": "Lockheed defense stock spread"},
        ],
        "asset_class": "agriculture",
        "expected_move_pct": 15.0,
        "probability": 0.58,
        "horizon_days": 45,
    },
    {
        "id": "P007",
        "name": "Natural Gas Supply Disruption → NG Price Spike",
        "description": "Pipeline disruption, LNG strike, or extreme weather drives natural gas +20-40%",
        "triggers": ["energy", "natural_disaster", "military"],
        "keywords": ["pipeline", "LNG", "natural gas", "Nord Stream", "gas supply", "storage"],
        "taxonomy_path": ["commodity", "commodity.energy", "commodity.energy.gas"],
        "historical_instances": [
            {"date": "2022-09-26", "event": "Nord Stream pipeline explosion", "ng_move": +18.0, "days": 5},
            {"date": "2021-02-10", "event": "Texas winter storm Uri", "ng_move": +40.0, "days": 3},
        ],
        "suggested_trades": [
            {"strategy": "Long Call", "underlying": "UNG", "rationale": "Natural gas ETF call"},
            {"strategy": "Bull Call Spread", "underlying": "NG=F", "rationale": "NG futures spread, capped risk"},
        ],
        "asset_class": "energy",
        "expected_move_pct": 25.0,
        "probability": 0.55,
        "horizon_days": 14,
    },
    {
        "id": "P008",
        "name": "Pandemic / Health Crisis → VIX Spike + Market Selloff",
        "description": "New pandemic scare or major health crisis → VIX spike, equity selloff, gold bid",
        "triggers": ["health_crisis"],
        "keywords": ["pandemic", "virus", "outbreak", "WHO", "lockdown", "COVID", "mpox", "H5N1"],
        "taxonomy_path": ["risk_off", "risk_off.pandemic"],
        "historical_instances": [
            {"date": "2020-02-24", "event": "COVID-19 global spread fear", "spx_move": -34.0, "days": 30},
            {"date": "2022-11-25", "event": "China COVID lockdowns", "spx_move": -3.5, "days": 3},
        ],
        "suggested_trades": [
            {"strategy": "Long Put", "underlying": "SPY", "rationale": "S&P 500 put for equity protection"},
            {"strategy": "Long Call", "underlying": "^VIX", "rationale": "VIX call for volatility spike"},
            {"strategy": "Long Call", "underlying": "GLD", "rationale": "Gold safe-haven call"},
        ],
        "asset_class": "indices",
        "expected_move_pct": -12.0,
        "probability": 0.45,
        "horizon_days": 30,
    },
    # ── New patterns P009-P023 ──────────────────────────────────────────────────
    {
        "id": "P009",
        "name": "BoJ YCC Break / Ultra-Loose Exit → JPY Surge",
        "description": "Bank of Japan abandons yield curve control or signals rate normalisation → JPY rally, JGB yields spike, global carry unwind",
        "triggers": ["political_speech"],
        "keywords": ["Bank of Japan", "BoJ", "YCC", "yield curve control", "yen", "JPY", "Ueda", "Kuroda", "JGB"],
        "taxonomy_path": ["monetary_policy", "monetary_policy.boj"],
        "historical_instances": [
            {"date": "2022-12-20", "event": "BoJ widens YCC band to ±0.5%", "usdjpy_move": -3.7, "days": 1},
            {"date": "2023-07-28", "event": "BoJ further loosens YCC to 1%", "usdjpy_move": -2.2, "days": 2},
            {"date": "2024-03-19", "event": "BoJ raises rates for first time since 2007", "usdjpy_move": -0.8, "days": 1},
        ],
        "suggested_trades": [
            {"strategy": "Long Put", "underlying": "FXY", "rationale": "JPY ETF put — gains as USD/JPY falls (yen strengthens)"},
            {"strategy": "Bear Put Spread", "underlying": "YCS", "rationale": "Ultra-short yen ETF put spread"},
            {"strategy": "Long Call", "underlying": "FXY", "rationale": "Direct long JPY via ETF call"},
        ],
        "asset_class": "forex",
        "expected_move_pct": -4.0,
        "probability": 0.60,
        "horizon_days": 7,
    },
    {
        "id": "P010",
        "name": "Regional Banking Crisis → Financial Contagion",
        "description": "Bank run or failure of regional lenders triggers systemic fear, credit spreads widen, equities sell off",
        "triggers": ["financial_crisis"],
        "keywords": ["bank run", "SVB", "Silicon Valley Bank", "FDIC", "deposit", "contagion", "credit suisse", "regional bank"],
        "taxonomy_path": ["economic", "economic.banking_crisis"],
        "historical_instances": [
            {"date": "2023-03-10", "event": "SVB collapse — largest US bank failure since 2008", "kre_move": -27.0, "days": 5},
            {"date": "2023-03-19", "event": "Credit Suisse emergency merger with UBS", "cs_move": -60.0, "days": 2},
            {"date": "2023-05-01", "event": "First Republic Bank seized by FDIC", "kre_move": -8.5, "days": 3},
        ],
        "suggested_trades": [
            {"strategy": "Long Put", "underlying": "KRE", "rationale": "Regional banks ETF put — direct contagion play"},
            {"strategy": "Long Call", "underlying": "GLD", "rationale": "Gold flight-to-safety bid"},
            {"strategy": "Bear Put Spread", "underlying": "XLF", "rationale": "Broad financial sector downside hedge"},
        ],
        "asset_class": "indices",
        "expected_move_pct": -15.0,
        "probability": 0.55,
        "horizon_days": 14,
    },
    {
        "id": "P011",
        "name": "Taiwan Strait Military Tension → Semis Selloff",
        "description": "PLA military exercises or blockade threat → global semiconductor supply fear, Taiwan semis/TSMC selloff",
        "triggers": ["military"],
        "keywords": ["Taiwan", "TSMC", "PLA", "strait", "China invasion", "semiconductor", "chip supply", "Pelosi"],
        "taxonomy_path": ["geopolitical", "territorial", "territorial.taiwan"],
        "historical_instances": [
            {"date": "2022-08-02", "event": "Pelosi visits Taiwan — PLA launches live-fire drills", "tsm_move": -6.5, "days": 3},
            {"date": "2023-04-08", "event": "PLA encirclement drills after Tsai-McCarthy meeting", "tsm_move": -3.2, "days": 2},
        ],
        "suggested_trades": [
            {"strategy": "Long Put", "underlying": "TSM", "rationale": "TSMC put — direct Taiwan semi exposure"},
            {"strategy": "Bear Put Spread", "underlying": "SOXX", "rationale": "Semis ETF put spread on supply disruption risk"},
            {"strategy": "Long Call", "underlying": "GLD", "rationale": "Safe-haven gold bid on regional military tension"},
        ],
        "asset_class": "equities",
        "expected_move_pct": -8.0,
        "probability": 0.50,
        "horizon_days": 14,
    },
    {
        "id": "P012",
        "name": "OPEC+ Surprise Production Cut → Crude Oil Spike",
        "description": "Unexpected OPEC+ output cut announcement drives Brent/WTI +5-15% in days",
        "triggers": ["energy", "political_speech"],
        "keywords": ["OPEC", "Saudi Arabia", "production cut", "barrel", "oil output", "supply cut"],
        "taxonomy_path": ["commodity", "commodity.energy", "commodity.energy.crude"],
        "historical_instances": [
            {"date": "2023-04-02", "event": "OPEC+ surprise 1.16Mb/d cut", "brent_move": +6.3, "days": 1},
            {"date": "2022-10-05", "event": "OPEC+ cuts 2Mb/d despite US pressure", "brent_move": +11.0, "days": 5},
            {"date": "2020-04-12", "event": "Historic OPEC+ 9.7Mb/d cut deal", "cl_move": +20.0, "days": 2},
        ],
        "suggested_trades": [
            {"strategy": "Bull Call Spread", "underlying": "USO", "rationale": "Oil ETF bull spread, defined risk"},
            {"strategy": "Long Call", "underlying": "CL=F", "rationale": "WTI crude futures call for directional upside"},
            {"strategy": "Long Call", "underlying": "XLE", "rationale": "Energy sector ETF call for broader sector play"},
        ],
        "asset_class": "energy",
        "expected_move_pct": 8.0,
        "probability": 0.65,
        "horizon_days": 14,
    },
    {
        "id": "P013",
        "name": "Fed Dovish Pivot → Risk-On Rally",
        "description": "Fed signals rate cuts ahead of schedule → equities rally, USD weakens, risk assets bid",
        "triggers": ["political_speech"],
        "keywords": ["Fed cut", "dovish", "pivot", "rate cut", "easing", "QE", "FOMC pause", "Jackson Hole"],
        "taxonomy_path": ["monetary_policy", "monetary_policy.pivot"],
        "historical_instances": [
            {"date": "2023-11-01", "event": "Fed holds rates, signals peak", "spy_move": +5.9, "days": 5},
            {"date": "2023-12-13", "event": "Fed dots signal 3 cuts in 2024", "spy_move": +3.5, "days": 2},
            {"date": "2019-07-31", "event": "First Fed cut since 2008", "spy_move": +2.1, "days": 1},
        ],
        "suggested_trades": [
            {"strategy": "Bull Call Spread", "underlying": "SPY", "rationale": "S&P 500 bull spread on risk-on rally"},
            {"strategy": "Long Call", "underlying": "QQQ", "rationale": "Nasdaq call — rate-sensitive growth names"},
            {"strategy": "Bear Put Spread", "underlying": "UUP", "rationale": "USD index downside on dovish turn"},
        ],
        "asset_class": "indices",
        "expected_move_pct": 5.0,
        "probability": 0.70,
        "horizon_days": 14,
    },
    {
        "id": "P014",
        "name": "US Debt Ceiling Standoff → T-Bill Stress",
        "description": "Congress deadlock on debt ceiling → short-term T-bill yields spike, credit risk perception rises, risk-off",
        "triggers": ["political_speech", "financial_crisis"],
        "keywords": ["debt ceiling", "default", "Treasury", "X-date", "fiscal cliff", "Congress", "spending"],
        "taxonomy_path": ["economic", "economic.debt_ceiling"],
        "historical_instances": [
            {"date": "2023-05-01", "event": "US 1-month T-bill yield spikes to 5.9%", "vix_move": +22.0, "days": 14},
            {"date": "2011-08-02", "event": "S&P downgrades US credit", "spy_move": -16.0, "days": 10},
            {"date": "2013-10-01", "event": "US government shutdown + debt ceiling standoff", "vix_move": +40.0, "days": 16},
        ],
        "suggested_trades": [
            {"strategy": "Long Call", "underlying": "GLD", "rationale": "Safe-haven gold on fiscal credibility risk"},
            {"strategy": "Bear Put Spread", "underlying": "SPY", "rationale": "Equity hedge during debt ceiling uncertainty"},
            {"strategy": "Long Put", "underlying": "TLT", "rationale": "Long-duration Treasury put on fiscal risk premium"},
        ],
        "asset_class": "indices",
        "expected_move_pct": -5.0,
        "probability": 0.55,
        "horizon_days": 21,
    },
    {
        "id": "P015",
        "name": "Iran Nuclear Escalation → Oil + Gold Spike",
        "description": "Iran nuclear programme breakthrough, US/Israel strike threat, or IAEA crisis → oil and gold both bid",
        "triggers": ["military", "sanctions"],
        "keywords": ["Iran", "nuclear", "enrichment", "IAEA", "sanctions", "Strait of Hormuz", "Israel strike"],
        "taxonomy_path": ["geopolitical", "sanctions", "sanctions.iran"],
        "historical_instances": [
            {"date": "2020-01-08", "event": "Iran missiles hit US bases in Iraq (after Soleimani)", "brent_move": +3.5, "days": 1},
            {"date": "2024-04-01", "event": "Israel strikes Iran consulate in Damascus", "brent_move": +4.0, "days": 3},
            {"date": "2024-04-13", "event": "Iran direct drone/missile attack on Israel", "brent_move": +3.0, "days": 1},
        ],
        "suggested_trades": [
            {"strategy": "Bull Call Spread", "underlying": "USO", "rationale": "Oil supply risk premium via ETF spread"},
            {"strategy": "Long Call", "underlying": "GLD", "rationale": "Dual safe-haven gold bid on military escalation"},
        ],
        "asset_class": "energy",
        "expected_move_pct": 6.0,
        "probability": 0.58,
        "horizon_days": 10,
    },
    {
        "id": "P016",
        "name": "North Korea Missile / Nuclear Test → Asian Risk-Off",
        "description": "DPRK ICBM launch or nuclear test → KRW selloff, Nikkei/Kospi dip, gold bid",
        "triggers": ["military"],
        "keywords": ["North Korea", "DPRK", "Kim Jong-un", "missile", "ICBM", "nuclear test", "Korea"],
        "taxonomy_path": ["geopolitical", "armed_conflict", "armed_conflict.asia_pacific"],
        "historical_instances": [
            {"date": "2022-11-18", "event": "DPRK fires ICBM over Japan", "krw_move": -0.8, "days": 1},
            {"date": "2017-09-03", "event": "North Korea 6th nuclear test", "krw_move": -1.5, "days": 2},
            {"date": "2023-03-16", "event": "DPRK fires ballistic missile", "nky_move": -0.4, "days": 1},
        ],
        "suggested_trades": [
            {"strategy": "Long Call", "underlying": "GLD", "rationale": "Safe-haven gold on regional military tension"},
            {"strategy": "Bear Put Spread", "underlying": "EWY", "rationale": "South Korea ETF put on escalation risk"},
        ],
        "asset_class": "indices",
        "expected_move_pct": -2.0,
        "probability": 0.45,
        "horizon_days": 7,
    },
    {
        "id": "P017",
        "name": "VIX Backwardation (Crisis Regime) → Vol Premium Collapse",
        "description": "VIX term structure inverts (spot > 3m future) signalling acute stress; historically mean-reverts fast → vol sellers reload",
        "triggers": ["financial_crisis"],
        "keywords": ["VIX", "backwardation", "volatility spike", "contango flip", "fear gauge", "VX futures"],
        "taxonomy_path": ["market_structure", "market_structure.volatility"],
        "historical_instances": [
            {"date": "2020-03-16", "event": "COVID crash peak backwardation VIX=82", "vix_move": +82.0, "days": 1},
            {"date": "2022-01-24", "event": "Fed tightening fear, VIX=38, backwardation", "vix_move": +38.0, "days": 14},
            {"date": "2018-02-05", "event": "Volmageddon — inverse VIX ETPs implode", "vix_move": +115.0, "days": 1},
        ],
        "suggested_trades": [
            {"strategy": "Bear Put Spread", "underlying": "SPY", "rationale": "Hedge equity downside during vol spike"},
            {"strategy": "Bull Call Spread", "underlying": "SPY", "rationale": "Mean-reversion entry once VIX normalises"},
            {"strategy": "Short Put", "underlying": "VXX", "rationale": "VIX ETF put — backwardation normalises quickly"},
        ],
        "asset_class": "indices",
        "expected_move_pct": -8.0,
        "probability": 0.60,
        "horizon_days": 10,
    },
    {
        "id": "P018",
        "name": "ECB Surprise Rate Move → EUR Volatility",
        "description": "ECB delivers surprise hike, cut, or emergency action outside meeting → EUR/USD sharp move",
        "triggers": ["political_speech"],
        "keywords": ["ECB", "Lagarde", "euro", "interest rate", "refi rate", "fragmentation", "BTP", "PEPP"],
        "taxonomy_path": ["monetary_policy", "monetary_policy.ecb"],
        "historical_instances": [
            {"date": "2022-07-21", "event": "ECB surprise 50bps hike (vs 25 expected)", "eurusd_move": +1.4, "days": 1},
            {"date": "2022-09-08", "event": "ECB hikes 75bps (largest ever)", "eurusd_move": +0.8, "days": 1},
            {"date": "2024-06-06", "event": "ECB first rate cut in 5 years", "eurusd_move": -0.3, "days": 1},
        ],
        "suggested_trades": [
            {"strategy": "Long Straddle", "underlying": "FXE", "rationale": "EUR/USD straddle on surprise — benefits from direction"},
            {"strategy": "Bull Call Spread", "underlying": "FXE", "rationale": "EUR call spread if dovish surprise expected"},
            {"strategy": "Bear Put Spread", "underlying": "FXE", "rationale": "EUR put spread if hawkish surprise"},
        ],
        "asset_class": "forex",
        "expected_move_pct": 1.5,
        "probability": 0.55,
        "horizon_days": 5,
    },
    {
        "id": "P019",
        "name": "Extreme Fear Sentiment → Contrarian Buy Signal",
        "description": "CNN F&G < 20 + AAII bears > 45% → historically strong mean-reversion buy within 4-8 weeks",
        "triggers": ["financial_crisis", "political_speech"],
        "keywords": ["fear & greed", "AAII", "bearish sentiment", "extreme fear", "capitulation", "put/call ratio"],
        "taxonomy_path": ["market_structure", "market_structure.sentiment"],
        "historical_instances": [
            {"date": "2022-10-13", "event": "CNN F&G=15, AAII Bears=60% — S&P bottoms", "spy_move": +19.0, "days": 60},
            {"date": "2020-03-23", "event": "COVID low — CNN F&G=2 extreme fear", "spy_move": +34.0, "days": 30},
            {"date": "2023-03-13", "event": "SVB panic — CNN F&G=22, bottom", "spy_move": +9.0, "days": 21},
        ],
        "suggested_trades": [
            {"strategy": "Bull Call Spread", "underlying": "SPY", "rationale": "Contrarian equity entry on extreme fear reading"},
            {"strategy": "Long Call", "underlying": "QQQ", "rationale": "Growth recovery play post-capitulation"},
        ],
        "asset_class": "indices",
        "expected_move_pct": 12.0,
        "probability": 0.68,
        "horizon_days": 45,
    },
    {
        "id": "P020",
        "name": "South China Sea Incident → Shipping / Energy Risk-Off",
        "description": "Collision, blockade, or military incident in SCS → shipping disruption, energy supply risk, regional selloff",
        "triggers": ["military"],
        "keywords": ["South China Sea", "Philippines", "Vietnam", "shipping lane", "Spratly", "Paracel", "Taiwan"],
        "taxonomy_path": ["geopolitical", "territorial", "territorial.south_china_sea"],
        "historical_instances": [
            {"date": "2023-08-05", "event": "China water cannon at Ayungin Shoal", "psei_move": -1.5, "days": 2},
            {"date": "2024-02-05", "event": "Heightened China-Philippines naval standoff", "eem_move": -0.8, "days": 3},
        ],
        "suggested_trades": [
            {"strategy": "Long Call", "underlying": "GLD", "rationale": "Safe-haven bid on regional naval tension"},
            {"strategy": "Bull Call Spread", "underlying": "USO", "rationale": "Oil supply disruption premium via ETF"},
            {"strategy": "Bear Put Spread", "underlying": "EEM", "rationale": "EM equity downside on Asian risk-off"},
        ],
        "asset_class": "energy",
        "expected_move_pct": 3.0,
        "probability": 0.40,
        "horizon_days": 10,
    },
    {
        "id": "P021",
        "name": "European Energy Crisis → EUR Selloff + NG Spike",
        "description": "Russian gas cutoff, LNG supply shortfall, or extreme winter demand drives European natural gas +30%+, EUR weakens",
        "triggers": ["energy", "military", "resource_scarcity"],
        "keywords": ["Europe energy", "gas storage", "TTF", "Gazprom", "LNG", "energy crisis", "winter", "sanctions"],
        "taxonomy_path": ["geopolitical", "armed_conflict", "armed_conflict.europe"],
        "historical_instances": [
            {"date": "2022-06-15", "event": "Gazprom cuts Nord Stream 1 flows 60%", "ttf_move": +60.0, "days": 10},
            {"date": "2022-08-26", "event": "Nord Stream shut entirely, gas crisis peak", "eurusd_move": -3.5, "days": 7},
        ],
        "suggested_trades": [
            {"strategy": "Long Call", "underlying": "UNG", "rationale": "US nat gas proxy for European supply squeeze"},
            {"strategy": "Bear Put Spread", "underlying": "FXE", "rationale": "EUR/USD put spread on energy terms-of-trade shock"},
        ],
        "asset_class": "energy",
        "expected_move_pct": 20.0,
        "probability": 0.52,
        "horizon_days": 30,
    },
    {
        "id": "P022",
        "name": "Inflation Surprise (CPI Hot Print) → Equity Drawdown",
        "description": "Above-consensus CPI print reignites rate hike fear → equities sell off, USD spikes, growth/duration hit hardest",
        "triggers": ["political_speech"],
        "keywords": ["CPI", "inflation", "hot print", "core inflation", "PCE", "surprise", "rate hike expectations"],
        "taxonomy_path": ["economic", "economic.inflation"],
        "historical_instances": [
            {"date": "2022-09-13", "event": "Aug CPI +8.3% vs 8.1% exp — worst drop in 2y", "spy_move": -4.3, "days": 1},
            {"date": "2023-02-14", "event": "Jan CPI +6.4% vs 6.2% exp", "spy_move": -1.4, "days": 1},
            {"date": "2022-06-10", "event": "May CPI +8.6% — 40-year high", "spy_move": -5.8, "days": 2},
        ],
        "suggested_trades": [
            {"strategy": "Bear Put Spread", "underlying": "QQQ", "rationale": "Nasdaq put spread — growth names most rate-sensitive"},
            {"strategy": "Long Call", "underlying": "UUP", "rationale": "USD call on hawkish repricing"},
            {"strategy": "Bear Put Spread", "underlying": "TLT", "rationale": "Long-duration bond downside on rate expectations reset"},
        ],
        "asset_class": "indices",
        "expected_move_pct": -4.0,
        "probability": 0.65,
        "horizon_days": 5,
    },
    {
        "id": "P023",
        "name": "Flash Crash / Liquidity Crisis → Rapid Selloff + Recovery",
        "description": "Sudden illiquidity event (algo cascade, forced deleveraging) → intraday/multi-day panic, followed by sharp recovery",
        "triggers": ["financial_crisis"],
        "keywords": ["flash crash", "liquidity crisis", "deleveraging", "margin call", "carry unwind", "forced selling"],
        "taxonomy_path": ["market_structure", "market_structure.liquidity"],
        "historical_instances": [
            {"date": "2024-08-05", "event": "JPY carry unwind — Nikkei -12% in one day", "nky_move": -12.4, "days": 1},
            {"date": "2020-03-12", "event": "COVID crash peak — S&P -10% in one session", "spy_move": -10.0, "days": 1},
            {"date": "2010-05-06", "event": "Flash Crash — Dow -1000pts intraday, recovers same day", "spy_move": -9.2, "days": 1},
        ],
        "suggested_trades": [
            {"strategy": "Long Put", "underlying": "SPY", "rationale": "Tail-risk put during systemic deleveraging"},
            {"strategy": "Bull Call Spread", "underlying": "SPY", "rationale": "Mean-reversion entry post-capitulation"},
            {"strategy": "Long Call", "underlying": "GLD", "rationale": "Gold flight-to-safety during liquidity panic"},
        ],
        "asset_class": "indices",
        "expected_move_pct": -10.0,
        "probability": 0.50,
        "horizon_days": 5,
    },
]


GEOPOLITICAL_RISK_WEIGHTS = {
    # Covers every category classify_news() can produce (data_fetcher.py GEO_KEYWORDS + "general"
    # fallback) — previously "sanctions", "elections" and "general" were silently excluded from the
    # aggregate score even when individually AI-scored high.
    "military": 0.20,
    "energy": 0.15,
    "political_speech": 0.12,
    "sanctions": 0.10,
    "trade_war": 0.10,
    "elections": 0.08,
    "natural_disaster": 0.08,
    "health_crisis": 0.08,
    "resource_scarcity": 0.04,
    "general": 0.05,
}


# A single extreme-severity article (e.g. imminent war, market-moving Fed shock) must not get diluted
# away just because other categories are quiet that day. This floor is driven by the single worst
# category score and stays inert below ~0.7, then ramps up steeply — solved so a lone 0.90 floors the
# final score at 50 (see MAX_CATEGORY_FLOOR_EXPONENT below).
MAX_CATEGORY_FLOOR_EXPONENT = 6.58


def compute_geo_risk_score(events: List[Dict[str, Any]]) -> Dict[str, Any]:
    """Compute a global geopolitical risk score 0-100 from recent events."""
    if not events:
        return {"score": 35, "level": "medium", "breakdown": {}}

    category_scores: Dict[str, float] = {}
    for event in events[:30]:
        cat = event.get("category", "general")
        impact = event.get("impact_score", 0.1)
        if cat in category_scores:
            category_scores[cat] = max(category_scores[cat], impact)
        else:
            category_scores[cat] = impact

    weighted = sum(
        category_scores.get(cat, 0) * weight
        for cat, weight in GEOPOLITICAL_RISK_WEIGHTS.items()
    )

    # Non-linear floor: the single most severe category score alone can force the score up,
    # even if it's the only hot category — breaks the "many quiet categories dilute one severe one" effect.
    max_category_impact = max(category_scores.values(), default=0.0)
    floor = max_category_impact ** MAX_CATEGORY_FLOOR_EXPONENT

    score = min(100, round(max(weighted, floor) * 100, 1))

    if score < 25:
        level = "low"
    elif score < 50:
        level = "medium"
    elif score < 75:
        level = "high"
    else:
        level = "extreme"

    return {
        "score": score,
        "level": level,
        "breakdown": {cat: round(v * 100, 1) for cat, v in category_scores.items()},
        "top_risks": sorted(category_scores.items(), key=lambda x: x[1], reverse=True)[:3],
    }


# Map asset_class → AI directional field produced by ai_score_news_batch()
_ASSET_AI_DIR: Dict[str, str] = {
    "energy":      "ai_dir_energy",
    "metals":      "ai_dir_metals",
    "indices":     "ai_dir_indices",
    "equities":    "ai_dir_indices",
}


def _compute_ai_alignment(events: List[Dict[str, Any]], pattern: Dict[str, Any]) -> Dict[str, Any]:
    """Return AI directional alignment between news signals and pattern expected direction.
    Uses ai_dir_* fields added by ai_score_news_batch().
    Returns alignment bonus (-25..+25) and metadata for display.
    """
    asset_class = pattern.get("asset_class", "")
    expected_positive = (pattern.get("expected_move_pct") or 0) > 0
    ai_field = _ASSET_AI_DIR.get(asset_class)

    ai_news = [e for e in events if e.get("ai_scored")]
    empty = {"ai_alignment": 0, "ai_contra_signal": False, "ai_insights": [], "ai_scored_count": 0}
    if not ai_news or not ai_field:
        return empty

    bullish = sum(1 for e in ai_news if e.get(ai_field) == "bullish")
    bearish = sum(1 for e in ai_news if e.get(ai_field) == "bearish")
    resolutions = sum(1 for e in ai_news if e.get("ai_resolution"))
    total = len(ai_news)

    # Positive alignment = news confirms pattern direction
    if expected_positive:
        raw = (bullish - bearish) / total
        contra = bearish > bullish or (resolutions > 0 and asset_class in ("energy", "metals"))
    else:
        raw = (bearish - bullish) / total
        contra = bullish > bearish

    bonus = int(round(max(-25.0, min(25.0, raw * 25))))

    insights = [
        e["ai_insight"] for e in ai_news
        if e.get("ai_insight") and e.get(ai_field, "neutral") != "neutral"
    ][:3]

    return {
        "ai_alignment": bonus,
        "ai_contra_signal": bool(contra),
        "ai_insights": insights,
        "ai_scored_count": len(ai_news),
    }


def match_patterns(events: List[Dict[str, Any]], patterns: Optional[List[Dict[str, Any]]] = None) -> List[Dict[str, Any]]:
    """Find which historical geo-patterns best match current event feed."""
    if not events:
        return []
    if patterns is None:
        patterns = GEO_PATTERNS

    current_categories = set(e.get("category", "") for e in events)
    current_tags = set()
    for e in events:
        current_tags.update(e.get("tags", []))
    current_text = " ".join(e.get("title", "") + " " + e.get("summary", "") for e in events[:20]).lower()

    matches = []
    for pattern in patterns:
        trigger_match = len(set(pattern["triggers"]) & current_categories) / len(pattern["triggers"])
        keyword_match = sum(1 for kw in pattern["keywords"] if kw.lower() in current_text) / len(pattern["keywords"])
        similarity = round((trigger_match * 0.5 + keyword_match * 0.5) * 100, 1)

        if similarity > 10:
            ai = _compute_ai_alignment(events, pattern)
            adjusted = max(0, min(100, similarity + ai["ai_alignment"]))
            matches.append({
                "pattern_id": pattern["id"],
                "name": pattern["name"],
                "description": pattern["description"],
                "similarity": round(adjusted, 1),
                "base_similarity": similarity,
                "suggested_trades": pattern["suggested_trades"],
                "asset_class": pattern["asset_class"],
                "expected_move_pct": pattern["expected_move_pct"],
                "probability": pattern["probability"],
                "horizon_days": pattern["horizon_days"],
                "historical_instances": pattern["historical_instances"],
                **ai,
            })

    return sorted(matches, key=lambda x: x["similarity"], reverse=True)[:5]


def generate_trade_ideas(pattern_matches: List[Dict[str, Any]], geo_score: Dict[str, Any]) -> List[Dict[str, Any]]:
    """Convert pattern matches into structured trade ideas with sizing for ~1000€."""
    ideas = []
    for pm in pattern_matches[:5]:
        for i, trade in enumerate(pm["suggested_trades"]):  # all suggested trades, not just first
            move = pm["expected_move_pct"]
            confidence = round(pm["probability"] * pm["similarity"] / 100 * 100)
            # Use trade-level asset_class if provided, else fall back to pattern-level
            asset_class = trade.get("asset_class") or pm["asset_class"]
            ideas.append({
                "id": f"IDEA-{pm['pattern_id']}-{i}-{trade['strategy'][:3].upper()}",
                "title": f"{trade['strategy']} on {trade['underlying']}",
                "rationale": f"[{pm['name']}] {trade['rationale']}. Expected move: {'+' if move > 0 else ''}{move}% in {pm['horizon_days']}d",
                "pattern": pm["name"],
                "asset_class": asset_class,
                "underlying": trade["underlying"],
                "strategy": trade["strategy"],
                "expected_move_pct": move,
                "confidence": min(95, confidence),
                "horizon_days": pm["horizon_days"],
                "capital_required": 1000,
                "risk_level": "high" if abs(move) > 15 else "medium",
                "pattern_similarity": pm["similarity"],
            })
    return ideas


def compute_pattern_relevance(
    events: List[Dict[str, Any]],
    patterns: Optional[List[Dict[str, Any]]] = None,
) -> List[Dict[str, Any]]:
    """Return ALL patterns with news-keyword relevance score + matching news snippets.
    Unlike match_patterns(), no similarity threshold — every active pattern is returned.
    """
    if patterns is None:
        patterns = GEO_PATTERNS

    current_categories = set(e.get("category", "") for e in events)
    current_text = " ".join(
        e.get("title", "") + " " + e.get("summary", "") for e in events[:30]
    ).lower()

    result = []
    for pattern in patterns:
        triggers_list = pattern.get("triggers", []) or []
        keywords_list = pattern.get("keywords", []) or []

        trigger_match = (
            len(set(triggers_list) & current_categories) / len(triggers_list)
            if triggers_list else 0
        )
        kw_hits = [kw for kw in keywords_list if kw.lower() in current_text]
        keyword_match = len(kw_hits) / len(keywords_list) if keywords_list else 0
        relevance = round((trigger_match * 0.5 + keyword_match * 0.5) * 100, 1)

        # Find matching news with which keywords triggered
        matching_news = []
        for e in events[:30]:
            text = (e.get("title", "") + " " + e.get("summary", "")).lower()
            hits = [kw for kw in keywords_list if kw.lower() in text]
            if hits:
                matching_news.append({
                    "title": e.get("title", ""),
                    "source": e.get("source", ""),
                    "date": str(e.get("date", ""))[:16],
                    "impact": round(e.get("impact_score", 0), 2),
                    "matched_keywords": hits,
                    "url": e.get("url", ""),
                })
        matching_news.sort(key=lambda x: x["impact"], reverse=True)

        ai = _compute_ai_alignment(events, pattern)
        adjusted_relevance = max(0, min(100, relevance + ai["ai_alignment"]))

        result.append({
            "pattern_id": pattern.get("id", ""),
            "name": pattern.get("name", ""),
            "description": pattern.get("description", ""),
            "asset_class": pattern.get("asset_class", ""),
            "relevance": round(adjusted_relevance, 1),
            "base_relevance": relevance,
            "keyword_hits": len(kw_hits),
            "keyword_total": len(keywords_list),
            "matched_keywords": kw_hits,
            "matching_news": matching_news[:5],
            "suggested_trades": pattern.get("suggested_trades", []),
            "expected_move_pct": pattern.get("expected_move_pct", 0),
            "probability": pattern.get("probability", 0),
            "horizon_days": pattern.get("horizon_days", 0),
            **ai,
        })

    result.sort(key=lambda x: x["relevance"], reverse=True)
    return result


def get_all_patterns() -> List[Dict[str, Any]]:
    return GEO_PATTERNS
