"""Scenario comparison engine for CCAR/DFAST stress test delta analytics.

Produces structured delta reports across Fed scenarios (baseline, adverse,
severely_adverse) showing capital impact waterfalls and sensitivity attribution.

Stdlib-only (ADR-007 Tier-0 compliant).
"""

from __future__ import annotations

import datetime
from dataclasses import dataclass, field
from typing import Any


@dataclass(frozen=True)
class ComparisonResult:
    """Structured delta report comparing stress test results across scenarios."""

    scenarios: list[str]
    quarter_labels: list[str]
    ecl_deltas: dict[str, list[float]]
    peak_loss_quarter: dict[str, str]
    capital_waterfall: dict[str, dict[str, float]]
    sensitivity_attribution: dict[str, float]
    generated_at: str = field(
        default_factory=lambda: datetime.datetime.now(datetime.UTC).isoformat()
    )

    def to_dict(self) -> dict[str, object]:
        """Return a JSON-serializable dict representation."""
        return {
            "scenarios": list(self.scenarios),
            "quarter_labels": list(self.quarter_labels),
            "ecl_deltas": {k: list(v) for k, v in self.ecl_deltas.items()},
            "peak_loss_quarter": dict(self.peak_loss_quarter),
            "capital_waterfall": {s: dict(wf) for s, wf in self.capital_waterfall.items()},
            "sensitivity_attribution": dict(self.sensitivity_attribution),
            "generated_at": self.generated_at,
        }


_BASELINE_MACROS: dict[str, float] = {
    "unemployment_rate": 0.04,
    "fed_funds_rate": 0.03,
    "gdp_growth": 0.02,
    "hpi": 220.0,
    "cpi": 260.0,
}

_SEVERELY_ADVERSE_MACROS: dict[str, float] = {
    "unemployment_rate": 0.10,
    "fed_funds_rate": 0.005,
    "gdp_growth": -0.04,
    "hpi": 150.0,
    "cpi": 280.0,
}

_MACRO_IMPACT_DIRECTION: dict[str, float] = {
    "unemployment_rate": 1.0,
    "fed_funds_rate": -1.0,
    "gdp_growth": -1.0,
    "hpi": -1.0,
    "cpi": 1.0,
}

_PPNR_OFFSET_FACTOR = 0.20


class ScenarioComparator:
    """Computes delta analytics across multiple stress test scenarios."""

    def __init__(self, reports: dict[str, Any], baseline: str = "baseline") -> None:
        if len(reports) < 2:
            msg = "ScenarioComparator requires at least 2 scenarios"
            raise ValueError(msg)
        self._reports = reports
        self._baseline = baseline

    def compare(self, starting_cet1: float = 0.12) -> ComparisonResult:
        horizon = self._horizon()
        quarter_labels = [f"Q{i + 1}" for i in range(horizon)]
        return ComparisonResult(
            scenarios=list(self._reports.keys()),
            quarter_labels=quarter_labels,
            ecl_deltas=self.compute_ecl_deltas(),
            peak_loss_quarter=self.identify_peak_loss_quarter(),
            capital_waterfall=self.build_capital_waterfall(starting_cet1),
            sensitivity_attribution=self.compute_sensitivity_attribution(),
        )

    def compute_ecl_deltas(self) -> dict[str, list[float]]:
        baseline_ecl = self._quarterly_ecl(self._baseline)
        result: dict[str, list[float]] = {}
        for name, report in self._reports.items():
            ecl = _get_quarterly_ecl(report)
            length = max(len(baseline_ecl), len(ecl))
            result[name] = [_safe_get(ecl, i) - _safe_get(baseline_ecl, i) for i in range(length)]
        return result

    def identify_peak_loss_quarter(self) -> dict[str, str]:
        horizon = self._horizon()
        labels = [f"Q{i + 1}" for i in range(horizon)]
        result: dict[str, str] = {}
        for name, report in self._reports.items():
            ecl = _get_quarterly_ecl(report)
            if not ecl:
                result[name] = "Q1"
                continue
            peak_idx = ecl.index(max(ecl))
            result[name] = labels[min(peak_idx, len(labels) - 1)]
        return result

    def build_capital_waterfall(self, starting_cet1: float = 0.12) -> dict[str, dict[str, float]]:
        result: dict[str, dict[str, float]] = {}
        for name, report in self._reports.items():
            loss_rate = _get_portfolio_loss_rate(report)
            credit_losses = starting_cet1 * loss_rate
            ppnr_offset = credit_losses * _PPNR_OFFSET_FACTOR
            ending_cet1 = starting_cet1 - credit_losses + ppnr_offset
            result[name] = {
                "starting_cet1": round(starting_cet1, 6),
                "credit_losses": round(credit_losses, 6),
                "ppnr_offset": round(ppnr_offset, 6),
                "ending_cet1": round(max(0.0, ending_cet1), 6),
            }
        return result

    def compute_sensitivity_attribution(self) -> dict[str, float]:
        stressed_name = (
            "severely_adverse"
            if "severely_adverse" in self._reports
            else max(
                (n for n in self._reports if n != self._baseline),
                key=lambda n: sum(_get_quarterly_ecl(self._reports[n])),
            )
        )
        baseline_report = self._reports.get(self._baseline, next(iter(self._reports.values())))
        baseline_ecl_total = sum(_get_quarterly_ecl(baseline_report))
        stressed_ecl_total = sum(_get_quarterly_ecl(self._reports[stressed_name]))
        ecl_divergence = abs(stressed_ecl_total - baseline_ecl_total)
        if ecl_divergence == 0.0:
            n = len(_BASELINE_MACROS)
            return {k: round(1.0 / n, 6) for k in _BASELINE_MACROS}
        raw: dict[str, float] = {}
        for var, bval in _BASELINE_MACROS.items():
            sval = _SEVERELY_ADVERSE_MACROS.get(var, bval)
            direction = _MACRO_IMPACT_DIRECTION.get(var, 1.0)
            magnitude = max(abs(bval), 1e-9)
            raw[var] = abs(sval - bval) * abs(direction) / magnitude
        total = sum(raw.values()) or 1.0
        return {k: round(v / total, 6) for k, v in raw.items()}

    def _quarterly_ecl(self, name: str) -> list[float]:
        r = self._reports.get(name)
        return _get_quarterly_ecl(r) if r is not None else []

    def _horizon(self) -> int:
        return max((len(_get_quarterly_ecl(r)) for r in self._reports.values()), default=9)


def _get_quarterly_ecl(report: object) -> list[float]:
    ecl = getattr(report, "quarterly_ecl", None)
    return [float(v) for v in ecl] if isinstance(ecl, list) else []


def _get_portfolio_loss_rate(report: object) -> float:
    return float(getattr(report, "portfolio_loss_rate", 0.0))


def _safe_get(lst: list[float], idx: int) -> float:
    return lst[idx] if idx < len(lst) else 0.0


__all__ = ["ComparisonResult", "ScenarioComparator"]
