"""ORB scenario test with streaming intraday fetch."""
from __future__ import annotations

import argparse
import asyncio
import json
import time
from pathlib import Path
from typing import Any

from apps.intraday_bt.oracle import make_intraday_oracle_client
from rich import box
from rich.console import Console
from rich.panel import Panel
from rich.progress import BarColumn, Progress, SpinnerColumn, TextColumn, TimeElapsedColumn
from rich.table import Table

from libs.common.config import get_settings
from libs.intraday.domain import ORBStrategyParams
from libs.oracle_client import OracleClient

from apps.intraday_bt.orb_research import (
    build_orb_research_context,
    compute_orb_rrs,
    filter_days,
    force_simple_returns,
    resolve_orb_config,
    simulate_orb_period,
)
from apps.intraday_bt.run import _latest_backtest_date

_console = Console(width=120)
_DEFAULT_START_DATE = "2022-01-01"


SCENARIO_REGISTRY: dict[str, dict[str, Any]] = {
    "bear_2022": {
        "description": "2022 bear market — fed hikes, tech selloff",
        "start": "2022-01-03",
        "end": "2022-12-30",
        "group": "regime",
        "expected": "negative (long-only headwind)",
    },
    "recovery_2023h1": {
        "description": "Early 2023 recovery from bear market lows",
        "start": "2023-01-03",
        "end": "2023-06-30",
        "group": "regime",
        "expected": "positive (volatility + momentum)",
    },
    "bull_2023h2": {
        "description": "Strong H2 2023 AI-driven bull run",
        "start": "2023-07-03",
        "end": "2023-12-29",
        "group": "regime",
        "expected": "positive (strong trend)",
    },
    "mixed_2024": {
        "description": "Mixed 2024 — rate-cut expectations, choppy mid-year",
        "start": "2024-01-02",
        "end": "2024-12-31",
        "group": "regime",
        "expected": "moderate",
    },
    "bull_2025": {
        "description": "2025 continuation bull market",
        "start": "2025-01-02",
        "end": "2025-12-31",
        "group": "regime",
        "expected": "positive",
    },
    "oos_2026": {
        "description": "Pure OOS holdout — 2026 YTD (never seen in IS)",
        "start": "2026-01-02",
        "end": None,
        "group": "regime",
        "expected": "validation only",
    },
    "no_rvol_filter": {
        "description": "Full period, RVOL filter disabled (min_rvol=0)",
        "start": None,
        "end": None,
        "group": "signal",
        "expected": "should degrade if RVOL adds value",
        "param_override": {"min_rvol": 0.0},
    },
    "random_ranking": {
        "description": "Full period, candidates ranked randomly",
        "start": None,
        "end": None,
        "group": "signal",
        "expected": "should degrade if ranking signal is real",
        "shuffle_candidates": True,
    },
}

SCENARIO_GROUPS: dict[str, list[str]] = {
    "regime": ["bear_2022", "recovery_2023h1", "bull_2023h2", "mixed_2024", "bull_2025", "oos_2026"],
    "signal": ["no_rvol_filter", "random_ranking"],
    "quick": ["bear_2022", "bull_2023h2", "oos_2026"],
    "all": list(SCENARIO_REGISTRY.keys()),
}


async def run_scenario(
    scenario_name: str,
    scenario_def: dict[str, Any],
    context,
    client: OracleClient,
    full_start: str,
    progress_prefix: str = "",
) -> dict[str, Any]:
    base_params = context.config.orb_strategy or ORBStrategyParams()
    if scenario_def.get("param_override"):
        base_params = base_params.model_copy(update=scenario_def["param_override"])

    sc_start = scenario_def.get("start") or full_start
    sc_end = scenario_def.get("end") or context.trading_days[-1]
    days = filter_days(context.trading_days, sc_start, sc_end)
    if len(days) < 10:
        return {"scenario": scenario_name, "verdict": "SKIP", "notes": f"Only {len(days)} days in range"}

    if progress_prefix:
        print(f"{progress_prefix}{scenario_name}: {days[0]} → {days[-1]} ({len(days)} days)")

    metrics = await simulate_orb_period(
        context,
        client,
        base_params,
        days,
        run_id=f"sc_{scenario_name[:8]}",
        shuffle_candidates_seed=1234 if scenario_def.get("shuffle_candidates") else None,
        progress_prefix=f"{progress_prefix}[{scenario_name}] " if progress_prefix else "",
    )
    return {
        "scenario": scenario_name,
        "period": f"{days[0]} → {days[-1]} ({len(days)} days)",
        "sharpe_ratio": metrics.sharpe_ratio or 0.0,
        "total_return_pct": (metrics.total_return_pct or 0.0) * 100,
        "max_drawdown_pct": abs((metrics.max_drawdown_pct or 0.0) * 100),
        "win_rate": (metrics.win_rate or 0.0) * 100,
        "profit_factor": metrics.profit_factor or 0.0,
        "total_trades": metrics.total_trades or 0,
    }


def _rrs_verdict(rrs: float) -> str:
    if rrs >= 70:
        return "ROBUST"
    if rrs >= 40:
        return "FRAGILE"
    return "OVERFIT"


def _print_scenario_table(scenario_results: dict[str, dict], config_name: str) -> None:
    table = Table(title=f"ORB Scenario Results — {config_name}", box=box.ROUNDED, width=118)
    table.add_column("Scenario", style="cyan", min_width=20)
    table.add_column("Period", style="dim", min_width=26)
    table.add_column("Sharpe", justify="right", min_width=7)
    table.add_column("Return%", justify="right", min_width=9)
    table.add_column("MaxDD%", justify="right", min_width=8)
    table.add_column("Win%", justify="right", min_width=6)
    table.add_column("PF", justify="right", min_width=6)
    table.add_column("Trades", justify="right", min_width=7)
    for name, result in scenario_results.items():
        if result.get("verdict") == "SKIP":
            table.add_row(name, "[dim]SKIP[/dim]", "-", "-", "-", "-", "-", "-")
            continue
        sr = result.get("sharpe_ratio", 0.0)
        ret = result.get("total_return_pct", 0.0)
        dd = result.get("max_drawdown_pct", 0.0)
        win = result.get("win_rate", 0.0)
        pf = result.get("profit_factor", 0.0)
        trades = result.get("total_trades", 0)
        period = result.get("period", "")
        sharpe_str = f"[green]{sr:.2f}[/green]" if sr >= 1.5 else f"[yellow]{sr:.2f}[/yellow]" if sr >= 0.5 else f"[red]{sr:.2f}[/red]" if sr < 0 else f"[dim]{sr:.2f}[/dim]"
        ret_str = f"[green]+{ret:.1f}%[/green]" if ret > 0 else f"[red]{ret:.1f}%[/red]"
        dd_str = f"[red]{dd:.1f}%[/red]" if dd > 15 else f"{dd:.1f}%"
        table.add_row(name, period, sharpe_str, ret_str, dd_str, f"{win:.0f}%", f"{pf:.2f}", str(trades))
    _console.print()
    _console.print(table)


def _score_bar(score: float) -> str:
    filled = int(round(score / 5))
    bar = "█" * filled + "░" * (20 - filled)
    color = "green" if score >= 70 else "yellow" if score >= 40 else "red"
    return f"[{color}]{bar}[/{color}] {score:.0f}/100"


def _print_rrs_panel(rrs: float, components: dict[str, float], config_name: str) -> None:
    verdict = _rrs_verdict(rrs)
    color = "green" if verdict == "ROBUST" else "yellow" if verdict == "FRAGILE" else "red"
    lines = [
        "[bold]ORB REGIME ROBUSTNESS SCORE (RRS)[/bold]",
        f"Strategy: [cyan]{config_name}[/cyan]",
        "",
        f"  Bear Survival      {_score_bar(components['bear_survival'])}",
        f"  Breadth            {_score_bar(components['breadth'])}",
        f"  Drawdown Resilience {_score_bar(components['drawdown_resilience'])}",
        f"  OOS Integrity      {_score_bar(components['oos_integrity'])}",
        f"  Stability          {_score_bar(components['stability'])}",
        "",
        f"  [bold]RRS: {_score_bar(rrs)}[/bold]",
        f"  [{color} bold]Verdict: {verdict}[/{color} bold]",
    ]
    _console.print()
    _console.print(Panel("\n".join(lines), box=box.DOUBLE, width=100))


async def _async_main(args: argparse.Namespace) -> int:
    if args.list or args.config is None:
        _console.print("\n[bold]Available scenarios:[/bold]")
        for name, scenario in SCENARIO_REGISTRY.items():
            start_str = scenario.get("start") or "full period"
            end_str = scenario.get("end") or "present"
            _console.print(f"  [cyan]{name:<22}[/cyan]  {start_str} → {end_str}  {scenario['description']}")
        _console.print("\n[bold]Scenario groups:[/bold]")
        for group, names in SCENARIO_GROUPS.items():
            _console.print(f"  [yellow]{group:<10}[/yellow]  {', '.join(names)}")
        return 0

    if args.scenario:
        if args.scenario not in SCENARIO_REGISTRY:
            _console.print(f"[red]Unknown scenario '{args.scenario}'[/red]")
            return 1
        scenario_names = [args.scenario]
    elif args.quick:
        scenario_names = SCENARIO_GROUPS["quick"]
    elif args.group:
        if args.group not in SCENARIO_GROUPS:
            _console.print(f"[red]Unknown group '{args.group}'[/red]")
            return 1
        scenario_names = SCENARIO_GROUPS[args.group]
    else:
        scenario_names = SCENARIO_GROUPS["all"]

    config_path, config = resolve_orb_config(args.config)
    config = force_simple_returns(config)
    config_slug = config_path.stem

    _console.print()
    _console.print(
        Panel(
            f"[bold]ORB SCENARIO TEST[/bold]\n"
            f"Config: [cyan]{config_path}[/cyan]\n"
            f"Scenarios: [yellow]{len(scenario_names)}[/yellow]  ({', '.join(scenario_names)})\n"
            f"Full data window: {args.start} → {args.end or _latest_backtest_date().isoformat()}",
            box=box.DOUBLE,
            width=100,
        )
    )

    settings = get_settings()
    async with make_intraday_oracle_client(settings) as client:
        context = await build_orb_research_context(
            config,
            args.start,
            args.end or _latest_backtest_date().isoformat(),
            client,
            print_progress=True,
        )

        t0 = time.time()
        scenario_results: dict[str, dict] = {}
        with Progress(
            SpinnerColumn(),
            TextColumn("[progress.description]{task.description}"),
            BarColumn(bar_width=30),
            "{task.completed}/{task.total}",
            TimeElapsedColumn(),
            console=_console,
        ) as progress:
            task = progress.add_task("Running scenarios...", total=len(scenario_names))
            for name in scenario_names:
                progress.update(task, description=f"[cyan]{name}[/cyan]")
                scenario_results[name] = await run_scenario(name, SCENARIO_REGISTRY[name], context, client, args.start)
                progress.advance(task)
            progress.update(task, description="Complete")
        elapsed = time.time() - t0

    _print_scenario_table(scenario_results, config_slug)
    non_skipped = {k: v for k, v in scenario_results.items() if v.get("verdict") != "SKIP" and "sharpe_ratio" in v}
    if len(non_skipped) >= 3:
        rrs, components = compute_orb_rrs(non_skipped)
        _print_rrs_panel(rrs, components, config_slug)
    else:
        rrs, components = 0.0, {}
        _console.print(f"\n[dim]RRS not computed: need ≥ 3 non-skipped scenarios, got {len(non_skipped)}[/dim]")

    _console.print(f"\n[dim]Elapsed: {elapsed:.0f}s[/dim]\n")

    if args.save:
        save_dir = Path("runs/intraday_orb")
        save_dir.mkdir(parents=True, exist_ok=True)
        out_path = save_dir / f"{config_slug}_scenario_report.json"
        payload = {
            "config": str(config_path),
            "scenarios_run": scenario_names,
            "rrs": rrs,
            "verdict": _rrs_verdict(rrs) if rrs > 0 else "N/A",
            "components": components,
            "results": scenario_results,
            "elapsed_seconds": round(elapsed, 1),
        }
        out_path.write_text(json.dumps(payload, indent=2))
        _console.print(f"[dim]Report saved → {out_path}[/dim]\n")
    return 0


def main() -> None:
    parser = argparse.ArgumentParser(
        prog="fithia2 intraday-scenario-test",
        description="ORB strategy regime robustness test.",
    )
    parser.add_argument("--config", "-c", default=None, help="YAML config path or strategy slug")
    parser.add_argument("--scenario", default=None, help="Run a single named scenario")
    parser.add_argument("--group", default=None, help="Run a scenario group: regime, signal, quick, all")
    parser.add_argument("--quick", action="store_true", help="Quick mode: run only bear_2022, bull_2023h2, oos_2026")
    parser.add_argument("--start", default=_DEFAULT_START_DATE, help=f"Start of full data window (default: {_DEFAULT_START_DATE})")
    parser.add_argument("--end", default=None, help="End of full data window (default: latest available)")
    parser.add_argument("--save", action="store_true", help="Save JSON report to runs/intraday_orb")
    parser.add_argument("--list", action="store_true", help="List all available scenarios and groups")
    args = parser.parse_args()
    raise SystemExit(asyncio.run(_async_main(args)))


if __name__ == "__main__":
    main()
