#!/usr/bin/env python3
"""SCENARIO_SHOCK_MATRIX_V1 — spec/formulas/domains/simulation.yaml.

Applies 5 deterministic stress multipliers to a base net-profit distribution to
produce per-scenario CE70/CVaR95. governance/todo/v8_9_p2_adoption_plan.yaml P2-A.

Hard rule (AGENTS.md): no base distribution -> all scenarios DATA_MISSING. Never
fabricate a shocked distribution from nothing.
"""
from __future__ import annotations

import argparse
import json
from pathlib import Path

ROOT = Path(__file__).resolve().parents[1]
DEFAULT_SIMULATION = ROOT / "Temp" / "forecast_simulation_engine_v1.json"
DEFAULT_DISTRIBUTION = ROOT / "Temp" / "net_profit_distribution_v1.json"
DEFAULT_OUT = ROOT / "Temp" / "scenario_shock_matrix_v1.json"

SCENARIO_DEFINITIONS = {
    "base_case": {"shock_multiplier": 1.0},
    "adverse_case": {"shock_multiplier": 1.5},
    "liquidity_drought_case": {"shock_multiplier": 1.3, "capacity_derate_pct": 40},
    "crisis_case": {"shock_multiplier": 2.0, "correlation_to_one": True},
    "fx_shock_case": {"shock_multiplier": 1.2, "applies_only_to": "foreign_assets"},
    "tax_cost_case": {"shock_multiplier": 1.0, "additional_cost_pct": 5},
}


def _load(path: Path) -> dict:
    if not path.exists():
        return {}
    try:
        data = json.loads(path.read_text(encoding="utf-8"))
        return data if isinstance(data, dict) else {}
    except Exception:
        return {}


def _quantile(sorted_values: list[float], q: float) -> float:
    if len(sorted_values) == 1:
        return sorted_values[0]
    pos = q * (len(sorted_values) - 1)
    lower = int(pos)
    upper = min(lower + 1, len(sorted_values) - 1)
    frac = pos - lower
    return sorted_values[lower] + (sorted_values[upper] - sorted_values[lower]) * frac


def _cvar95(sorted_values: list[float]) -> float:
    threshold_idx = max(1, int(len(sorted_values) * 0.05))
    tail = sorted_values[:threshold_idx]
    return sum(tail) / len(tail)


def apply_shock(distribution: list[float], scenario_id: str) -> list[float]:
    scenario = SCENARIO_DEFINITIONS[scenario_id]
    multiplier = scenario["shock_multiplier"]
    additional_cost_pct = scenario.get("additional_cost_pct", 0)
    shocked = []
    for v in distribution:
        value = v * multiplier if v < 0 else v / multiplier
        if additional_cost_pct:
            value -= abs(value) * (additional_cost_pct / 100.0)
        shocked.append(value)
    return shocked


def evaluate_scenario(distribution: list[float] | None, scenario_id: str) -> dict:
    if not distribution:
        return {
            "scenario_id": scenario_id,
            "scenario_ce70_krw": None,
            "scenario_cvar95_krw": None,
            "gate": "DATA_MISSING",
        }
    shocked = sorted(apply_shock(distribution, scenario_id))
    return {
        "scenario_id": scenario_id,
        "scenario_ce70_krw": _quantile(shocked, 0.30),
        "scenario_cvar95_krw": _cvar95(shocked),
        "gate": "PASS",
    }


def main() -> int:
    ap = argparse.ArgumentParser()
    ap.add_argument("--distribution", default=str(DEFAULT_DISTRIBUTION))
    ap.add_argument("--out", default=str(DEFAULT_OUT))
    args = ap.parse_args()

    doc = _load(Path(args.distribution))
    distribution = doc.get("net_profit_distribution_after_tax_fee_slippage")
    distribution = [float(v) for v in distribution] if isinstance(distribution, list) and distribution else None

    scenario_results = [evaluate_scenario(distribution, scenario_id) for scenario_id in SCENARIO_DEFINITIONS]

    result = {
        "formula_id": "SCENARIO_SHOCK_MATRIX_V1",
        "gate": "PASS" if distribution else "DATA_MISSING",
        "scenario_results": scenario_results,
        "source_paths": [str(Path(args.distribution))],
    }
    out = Path(args.out)
    out.write_text(json.dumps(result, ensure_ascii=False, indent=2), encoding="utf-8")
    print(json.dumps(result, ensure_ascii=False, indent=2))
    return 0


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