from __future__ import annotations

import argparse
from dataclasses import dataclass
from pathlib import Path
from typing import Mapping, Sequence

from config import BotConfig, RuntimePaths
from historical_replay import Candle, fetch_hyperliquid_candles
from near_miss import effective_required_drop_pct, leverage_for_coin, roe_crash_required_drop_pct


@dataclass(frozen=True)
class CrashFrequency:
    coin: str
    leverage: float
    required_drop_pct: float
    roe_equivalent_drop_pct: float
    events: int
    candles: int
    days: float
    events_per_day: float
    median_gap_hours: float | None
    worst_drop_pct: float


def _rolling_drop_pct(candles: Sequence[Candle], idx: int, window_candles: int) -> float:
    start = max(0, idx - window_candles + 1)
    high = max(row.high for row in candles[start: idx + 1])
    close = candles[idx].close
    if high <= 0 or close <= 0:
        return 0.0
    return ((close - high) / high) * 100.0


def count_crash_events(
    coin: str,
    candles: Sequence[Candle],
    *,
    cfg: BotConfig,
    window_minutes: int | None = None,
    interval_minutes: int = 5,
) -> CrashFrequency:
    window_minutes = window_minutes or cfg.crash_window_minutes
    window_candles = max(1, round(window_minutes / interval_minutes))
    required = effective_required_drop_pct(cfg, coin)
    drops = [_rolling_drop_pct(candles, idx, window_candles) for idx in range(len(candles))]
    event_ts: list[float] = []
    in_event = False
    for candle, drop in zip(candles, drops):
        hit = drop <= -required
        if hit and not in_event:
            event_ts.append(float(candle.ts))
        in_event = hit
    gaps = [(b - a) / 3600 for a, b in zip(event_ts, event_ts[1:])]
    median_gap = sorted(gaps)[len(gaps) // 2] if gaps else None
    days = ((float(candles[-1].ts) - float(candles[0].ts)) / 86400) if len(candles) >= 2 else 0.0
    return CrashFrequency(
        coin=coin.upper(),
        leverage=leverage_for_coin(cfg, coin),
        required_drop_pct=round(required, 4),
        roe_equivalent_drop_pct=round(roe_crash_required_drop_pct(cfg, coin), 4),
        events=len(event_ts),
        candles=len(candles),
        days=round(days, 2),
        events_per_day=round(len(event_ts) / days, 4) if days > 0 else 0.0,
        median_gap_hours=round(median_gap, 2) if median_gap is not None else None,
        worst_drop_pct=round(min(drops), 4) if drops else 0.0,
    )


def render_frequency_report(rows: Sequence[CrashFrequency], *, window_minutes: int, hours_back: int) -> str:
    lines = [
        "Crash Frequency Report",
        f"Window: {window_minutes}m rolling high-to-close drop",
        f"History: {hours_back / 24:.1f}d",
        "Threshold: max(strategy flash_crash_trigger_pct, crash_roe_trigger_pct / coin_leverage)",
        "",
    ]
    for row in sorted(rows, key=lambda item: (-item.events_per_day, item.coin)):
        gap = "n/a" if row.median_gap_hours is None else f"{row.median_gap_hours:.1f}h"
        lines.append(
            f"{row.coin}: lev={row.leverage:g}x required={row.required_drop_pct:.2f}% "
            f"(15% ROE equiv={row.roe_equivalent_drop_pct:.2f}%), coverage={row.days:.1f}d, "
            f"events={row.events}, per_day={row.events_per_day:.3f}, median_gap={gap}, worst={row.worst_drop_pct:.2f}%"
        )
    return "\n".join(lines)


def main(argv: list[str] | None = None) -> int:
    cfg = BotConfig.from_file()
    paths = RuntimePaths.from_config(cfg)
    parser = argparse.ArgumentParser(description="Measure leverage-normalized crypto crash frequency.")
    parser.add_argument("--coins", nargs="*", default=list(cfg.allowed_coins) or ["BTC", "ETH", "SOL", "HYPE"])
    parser.add_argument("--hours-back", type=int, default=24 * 90)
    parser.add_argument("--interval", default="5m")
    parser.add_argument("--window-minutes", type=int, default=15)
    args = parser.parse_args(argv)

    interval_minutes = int(args.interval.rstrip("m")) if args.interval.endswith("m") else 5
    cache_dir = paths.runtime_dir / "candle_cache"
    rows: list[CrashFrequency] = []
    for coin in args.coins:
        candles = fetch_hyperliquid_candles(coin.upper(), interval=args.interval, hours_back=args.hours_back, cache_dir=cache_dir, tls_verify=cfg.tls_verify)
        if candles:
            rows.append(count_crash_events(coin.upper(), candles, cfg=cfg, window_minutes=args.window_minutes, interval_minutes=interval_minutes))
    report = render_frequency_report(rows, window_minutes=args.window_minutes, hours_back=args.hours_back)
    report_path = paths.reports_dir / f"crash_frequency_{args.hours_back // 24}d_{args.interval}.txt"
    report_path.write_text(report, encoding="utf-8")
    print(report)
    print(f"Report: {report_path}")
    return 0


if __name__ == "__main__":
    raise SystemExit(main())
