"""Market data providers.

MVP ships a deterministic mock provider (seeded per symbol per day, so results
are stable within a trading day). The `MarketDataProvider` interface is the
integration point for Alpaca / Tradier (see live_data.py).
"""
import math
import random
from datetime import date, datetime, timedelta

UNIVERSE = [
    ("TSLA", "Tesla Inc", 250.0), ("NVDA", "NVIDIA Corp", 130.0),
    ("AMD", "Advanced Micro Devices", 160.0), ("META", "Meta Platforms", 500.0),
    ("AAPL", "Apple Inc", 210.0), ("AMZN", "Amazon.com", 185.0),
    ("MSFT", "Microsoft Corp", 430.0), ("GOOGL", "Alphabet Inc", 175.0),
    ("NFLX", "Netflix Inc", 650.0), ("COIN", "Coinbase Global", 240.0),
    ("MARA", "Marathon Digital", 22.0), ("RIOT", "Riot Platforms", 12.0),
    ("PLTR", "Palantir Technologies", 26.0), ("SOFI", "SoFi Technologies", 8.0),
    ("SQ", "Block Inc", 70.0), ("SHOP", "Shopify Inc", 65.0),
    ("UBER", "Uber Technologies", 72.0), ("ABNB", "Airbnb Inc", 150.0),
    ("SNAP", "Snap Inc", 12.0), ("ROKU", "Roku Inc", 62.0),
    ("DKNG", "DraftKings Inc", 40.0), ("RIVN", "Rivian Automotive", 12.0),
    ("LCID", "Lucid Group", 3.5), ("NIO", "NIO Inc", 5.0),
    ("BABA", "Alibaba Group", 78.0), ("CRM", "Salesforce Inc", 260.0),
    ("ORCL", "Oracle Corp", 140.0), ("INTC", "Intel Corp", 32.0),
    ("MU", "Micron Technology", 120.0), ("AVGO", "Broadcom Inc", 1400.0),
    ("SMCI", "Super Micro Computer", 800.0), ("ARM", "Arm Holdings", 130.0),
    ("CRWD", "CrowdStrike Holdings", 350.0), ("NET", "Cloudflare Inc", 80.0),
    ("DDOG", "Datadog Inc", 120.0), ("MDB", "MongoDB Inc", 240.0),
    ("SPY", "SPDR S&P 500 ETF", 545.0), ("QQQ", "Invesco QQQ Trust", 470.0),
    ("IWM", "iShares Russell 2000", 205.0), ("XOM", "Exxon Mobil", 112.0),
]


CRYPTO_UNIVERSE = [
    ("BTC/USD", "Bitcoin", 68000.0), ("ETH/USD", "Ethereum", 3500.0),
    ("SOL/USD", "Solana", 150.0), ("LINK/USD", "Chainlink", 18.0),
    ("AVAX/USD", "Avalanche", 35.0), ("DOGE/USD", "Dogecoin", 0.2),
    ("XRP/USD", "XRP", 0.6),
]


def is_crypto(symbol: str) -> bool:
    return "/" in symbol


class MarketDataProvider:
    """Interface for market data providers (implement for Alpaca/Tradier)."""

    def get_universe(self) -> list[dict]:
        raise NotImplementedError

    def get_crypto_universe(self) -> list[dict]:
        return [{"symbol": s, "company": c, "base_price": p} for s, c, p in CRYPTO_UNIVERSE]

    def get_history(self, symbol: str, days: int = 60) -> list[dict]:
        raise NotImplementedError

    def get_quote(self, symbol: str) -> dict:
        raise NotImplementedError

    def get_options_chain(self, symbol: str) -> list[dict]:
        raise NotImplementedError


class MockMarketDataProvider(MarketDataProvider):
    """Deterministic pseudo-random data; stable per symbol per calendar day."""

    def __init__(self, seed_date: date | None = None):
        self.seed_date = seed_date or date.today()

    def _rng(self, symbol: str, tag: str = "") -> random.Random:
        return random.Random(f"{symbol}:{self.seed_date.isoformat()}:{tag}")

    def get_universe(self) -> list[dict]:
        return [{"symbol": s, "company": c, "base_price": p} for s, c, p in UNIVERSE]

    def get_history(self, symbol: str, days: int = 60) -> list[dict]:
        crypto = is_crypto(symbol)
        if crypto:
            base = next((p for s, _, p in CRYPTO_UNIVERSE if s == symbol), 100.0)
        else:
            base = next((p for s, _, p in UNIVERSE if s == symbol), 100.0)
        rng = self._rng(symbol, "hist")
        drift = rng.uniform(-0.002, 0.003)
        # Crypto trades 24/7 with roughly double equity volatility
        vol = rng.uniform(0.025, 0.07) if crypto else rng.uniform(0.012, 0.045)
        bars = []
        price = base * rng.uniform(0.85, 1.1)
        d = datetime.now() - timedelta(days=days)
        for i in range(days):
            d += timedelta(days=1)
            if not crypto and d.weekday() >= 5:
                continue
            change = rng.gauss(drift, vol)
            o = price
            c = max(0.5, price * (1 + change))
            hi = max(o, c) * (1 + abs(rng.gauss(0, vol / 2)))
            lo = min(o, c) * (1 - abs(rng.gauss(0, vol / 2)))
            v = int(rng.uniform(0.6, 3.0) * 10_000_000)
            bars.append({
                "date": d.strftime("%Y-%m-%d"),
                "open": round(o, 2), "high": round(hi, 2),
                "low": round(lo, 2), "close": round(c, 2), "volume": v,
            })
            price = c
        return bars

    def get_quote(self, symbol: str) -> dict:
        hist = self.get_history(symbol)
        last, prev = hist[-1], hist[-2]
        avg_vol = sum(b["volume"] for b in hist[-20:]) / 20
        return {
            "symbol": symbol,
            "price": last["close"],
            "change_pct": round((last["close"] / prev["close"] - 1) * 100, 2),
            "volume": last["volume"],
            "relative_volume": round(last["volume"] / avg_vol, 2),
        }

    def get_options_chain(self, symbol: str) -> list[dict]:
        quote = self.get_quote(symbol)
        price = quote["price"]
        rng = self._rng(symbol, "opt")
        iv_base = rng.uniform(0.35, 0.95)
        expiry = (self.seed_date + timedelta(days=30)).isoformat()
        step = max(0.5, round(price * 0.025, 1))
        chain = []
        for i in range(-4, 5):
            strike = round(price + i * step, 1)
            for opt_type in ("call", "put"):
                moneyness = (price - strike) / price * (1 if opt_type == "call" else -1)
                delta = max(0.03, min(0.97, 0.5 + moneyness * 6))
                mid = max(0.05, price * iv_base * 0.12 * math.exp(-abs(moneyness) * 8) + max(0.0, moneyness * price))
                spread = mid * rng.uniform(0.02, 0.15)
                chain.append({
                    "symbol": symbol,
                    "type": opt_type,
                    "strike": strike,
                    "expiry": expiry,
                    "bid": round(mid - spread / 2, 2),
                    "ask": round(mid + spread / 2, 2),
                    "iv": round(iv_base * rng.uniform(0.9, 1.15), 3),
                    "delta": round(delta if opt_type == "call" else delta - 1, 3),
                    "gamma": round(rng.uniform(0.005, 0.05), 4),
                    "theta": round(-mid * rng.uniform(0.01, 0.04), 3),
                    "vega": round(price * rng.uniform(0.0008, 0.002), 3),
                    "open_interest": int(rng.uniform(50, 20000) * math.exp(-abs(i) * 0.4)),
                    "volume": int(rng.uniform(10, 8000) * math.exp(-abs(i) * 0.5)),
                })
        return chain


def bars_are_fresh(bars: list[dict], max_age_days: int | None = None) -> bool:
    """Stale-data circuit breaker (audit Phase 5): signals must never be
    generated from old bars. True when the latest bar is recent enough."""
    import os

    if max_age_days is None:
        try:
            max_age_days = int(os.getenv("DATA_FRESHNESS_MAX_DAYS", "5"))
        except ValueError:
            max_age_days = 5
    if not bars:
        return False
    try:
        last = date.fromisoformat(str(bars[-1]["date"])[:10])
    except (KeyError, ValueError):
        return False
    return (date.today() - last).days <= max_age_days


def get_provider() -> MarketDataProvider:
    """Provider factory.

    DATA_PROVIDER=live (+ ALPACA_API_KEY/SECRET and/or TRADIER_API_TOKEN)
    activates real market data; anything else - or missing keys - uses the
    deterministic mock so the platform always works.
    """
    import os

    if os.getenv("DATA_PROVIDER", "mock").lower() == "live":
        has_alpaca = bool(os.getenv("ALPACA_API_KEY") and os.getenv("ALPACA_API_SECRET"))
        has_tradier = bool(os.getenv("TRADIER_API_TOKEN"))
        if has_alpaca or has_tradier:
            from .live_data import LiveMarketDataProvider

            return LiveMarketDataProvider()
    return MockMarketDataProvider()
