"""Probe cash parking variants on top of a strategy manifest.

Examples:
    python -m apps.tools.parking_strategy_probe \
      --config configs/experiments/return_max_long_v7.70.json \
      --start 2022 --end 2026 \
      --period-start y2026=2026-01-01 \
      --preset ve_10_10 --preset vt_24_t13

    python -m apps.tools.parking_strategy_probe \
      --config configs/experiments/return_max_long_v7.70.json \
      --start 2022 --end 2026 \
      --override dynamic_vol_24='{"cash_parking_enabled": true, "cash_parking_symbol": "dynamic", "cash_parking_gate_mode": "volatility", "cash_parking_gate_vol_lookback": 20, "cash_parking_gate_vol_threshold": 0.24}'
"""
from __future__ import annotations

import argparse
import datetime as dt
import json
from dataclasses import dataclass
from typing import Any

from apps.backtester.run import (
    BacktestRunner,
    _build_merged_snapshot_store,
    load_manifest,
    resolve_config,
)
from libs.common.logging import configure_logging


@dataclass(frozen=True)
class VariantSpec:
    name: str
    preset: str | None
    risk_overrides: dict[str, Any]


def _parse_date(value: str, *, is_end: bool = False) -> dt.date:
    parts = value.split("-")
    if len(parts) == 1 and len(value) == 4 and value.isdigit():
        year = int(value)
        return dt.date(year, 12, 31) if is_end else dt.date(year, 1, 1)
    if len(parts) == 2 and all(part.isdigit() for part in parts):
        year = int(parts[0])
        month = int(parts[1])
        if is_end:
            next_month = dt.date(year + (month // 12), (month % 12) + 1, 1)
            return next_month - dt.timedelta(days=1)
        return dt.date(year, month, 1)
    return dt.date.fromisoformat(value)


def _parse_period_start(value: str) -> tuple[str, dt.date]:
    if "=" in value:
        label, raw_date = value.split("=", 1)
    else:
        label, raw_date = value, value
    return label, _parse_date(raw_date)


def _parse_override(value: str) -> VariantSpec:
    if "=" not in value:
        raise argparse.ArgumentTypeError("--override format: name=<json>")
    name, payload = value.split("=", 1)
    try:
        risk_overrides = json.loads(payload)
    except json.JSONDecodeError as exc:
        raise argparse.ArgumentTypeError(f"invalid JSON for override '{name}': {exc}") from exc
    if not isinstance(risk_overrides, dict):
        raise argparse.ArgumentTypeError("override JSON must decode to an object")
    return VariantSpec(name=name, preset=None, risk_overrides=risk_overrides)


def _period_stats(curve: list[Any], start_date: dt.date) -> dict[str, Any] | None:
    points = [(state.date, float(state.equity)) for state in curve if state.date >= start_date]
    if not points:
        return None
    start_equity = points[0][1]
    peak = start_equity
    max_dd_pct = 0.0
    for _, equity in points:
        peak = max(peak, equity)
        if peak > 0:
            max_dd_pct = max(max_dd_pct, (peak - equity) / peak * 100.0)
    end_equity = points[-1][1]
    return {
        "start_date": points[0][0].isoformat(),
        "end_date": points[-1][0].isoformat(),
        "return_pct": (end_equity / start_equity - 1.0) * 100.0 if start_equity else 0.0,
        "max_dd_pct": max_dd_pct,
    }


def _build_variants(args: argparse.Namespace) -> list[VariantSpec]:
    variants = [VariantSpec(name="no_parking", preset=None, risk_overrides={})]
    for preset in args.preset or []:
        variants.append(VariantSpec(name=preset, preset=preset, risk_overrides={}))
    variants.extend(args.override or [])
    return variants


def main() -> None:
    parser = argparse.ArgumentParser(description="Probe strategy cash parking variants")
    parser.add_argument("--config", required=True, help="Experiment manifest JSON path")
    parser.add_argument("--start", required=True, help="Start date (YYYY, YYYY-MM, YYYY-MM-DD)")
    parser.add_argument("--end", required=True, help="End date (YYYY, YYYY-MM, YYYY-MM-DD)")
    parser.add_argument(
        "--period-start",
        action="append",
        default=[],
        help="Sub-period start. Format: label=YYYY-MM-DD (repeatable).",
    )
    parser.add_argument(
        "--preset",
        action="append",
        default=[],
        help="Parking preset name to evaluate (repeatable).",
    )
    parser.add_argument(
        "--override",
        action="append",
        type=_parse_override,
        default=[],
        help="Named risk override. Format: name=<json> (repeatable).",
    )
    parser.add_argument(
        "--json",
        action="store_true",
        help="Emit machine-readable JSON only.",
    )
    parser.add_argument(
        "--capital",
        type=float,
        default=10_000.0,
        help="Initial equity for the probe (default: 10000).",
    )
    args = parser.parse_args()

    configure_logging("WARNING")

    start_date = _parse_date(args.start)
    end_date = _parse_date(args.end, is_end=True)
    period_starts = [_parse_period_start(value) for value in args.period_start]
    variants = _build_variants(args)

    manifest = load_manifest(args.config)
    base_config = resolve_config(manifest)
    store = _build_merged_snapshot_store(
        manifest,
        base_config,
        snapshot_dir_override=None,
    ).slice_by_date_range(start_date, end_date)

    rows: list[dict[str, Any]] = []
    for variant in variants:
        config = resolve_config(manifest)
        if variant.preset:
            config.risk.cash_parking_preset = variant.preset
            config.risk.apply_parking_preset()
        for key, value in variant.risk_overrides.items():
            setattr(config.risk, key, value)

        runner = BacktestRunner(
            manifest=manifest,
            config=config,
            store=store,
            initial_equity=args.capital,
            split_name="parking_probe",
        )
        result = runner.run(output_root=None)
        row = {
            "variant": variant.name,
            "full_return_pct": round(result.metrics.total_return_pct or 0.0, 2),
            "full_dd_pct": round(result.metrics.max_drawdown_pct or 0.0, 2),
            "full_sharpe": round(result.metrics.sharpe_ratio or 0.0, 3),
            "trade_count": result.metrics.trade_count,
            "last_date": runner._equity_curve[-1].date.isoformat() if runner._equity_curve else None,
        }
        for label, period_start in period_starts:
            stats = _period_stats(runner._equity_curve, period_start)
            row[label] = {
                "return_pct": round(stats["return_pct"], 2) if stats else None,
                "max_dd_pct": round(stats["max_dd_pct"], 2) if stats else None,
                "start_date": stats["start_date"] if stats else None,
                "end_date": stats["end_date"] if stats else None,
            }
        rows.append(row)

    if args.json:
        print(json.dumps(rows, ensure_ascii=False, indent=2))
        return

    period_labels = [label for label, _ in period_starts]
    header = ["variant", "full_return", "full_dd", "full_sharpe", "trades", "last_date"]
    for label in period_labels:
        header.extend([f"{label}_return", f"{label}_dd"])
    print("\t".join(header))
    for row in rows:
        fields = [
            row["variant"],
            f"{row['full_return_pct']:.2f}",
            f"{row['full_dd_pct']:.2f}",
            f"{row['full_sharpe']:.3f}",
            str(row["trade_count"]),
            str(row["last_date"] or "-"),
        ]
        for label in period_labels:
            stats = row.get(label) or {}
            fields.extend([
                "-" if stats.get("return_pct") is None else f"{stats['return_pct']:.2f}",
                "-" if stats.get("max_dd_pct") is None else f"{stats['max_dd_pct']:.2f}",
            ])
        print("\t".join(fields))


if __name__ == "__main__":
    main()
