"""Tests for CECL/CCAR Regulatory Report Export Module."""

from __future__ import annotations

import dataclasses
import json
import types

import pytest

from orxaq.fabric.regulatory_export import (
    CCARProjectionBuilder,
    RegulatoryExporter,
    RegulatoryPackage,
    ScenarioComparisonTable,
    ScheduleH1Builder,
    build_regulatory_package,
)

# ---------------------------------------------------------------------------
# Helper stubs — keep tests self-contained, no credit module imports needed
# ---------------------------------------------------------------------------


def _make_cecl(segments=None, ecl=1000.0, pd=0.05, lgd=0.45, ead=20000.0):
    """Return a SimpleNamespace mimicking a CECLResult."""
    if segments is None:
        segments = {
            "Retail": {"ead": 10000.0, "pd": 0.04, "lgd": 0.45, "ecl": 400.0},
            "Corp": {"ead": 10000.0, "pd": 0.06, "lgd": 0.45, "ecl": 600.0},
        }
    return types.SimpleNamespace(segments=segments, ecl=ecl, pd=pd, lgd=lgd, ead=ead)


def _make_stress(quarterly_ecl=None, confidence_intervals=None):
    """Return a SimpleNamespace mimicking a StressTestReport."""
    if quarterly_ecl is None:
        quarterly_ecl = [100.0 * (i + 1) for i in range(9)]
    if confidence_intervals is None:
        confidence_intervals = [(q * 0.9, q * 1.1) for q in quarterly_ecl]
    return types.SimpleNamespace(
        quarterly_ecl=quarterly_ecl,
        confidence_intervals=confidence_intervals,
    )


# ---------------------------------------------------------------------------
# TestRegulatoryPackage
# ---------------------------------------------------------------------------


class TestRegulatoryPackage:
    def test_frozen(self) -> None:
        pkg = RegulatoryPackage(
            as_of_date="2025-12-31",
            institution_id="TEST001",
            submission_period="2025Q4",
            schedule_h1=[],
            ccar_projection=[],
            scenario_comparison=[],
        )
        with pytest.raises((dataclasses.FrozenInstanceError, AttributeError)):
            pkg.as_of_date = "changed"  # type: ignore[misc]

    def test_fields(self) -> None:
        pkg = RegulatoryPackage(
            as_of_date="2025-12-31",
            institution_id="BANK001",
            submission_period="2025Q4",
            schedule_h1=[{"segment": "Retail"}],
            ccar_projection=[{"quarter": "Q1"}],
            scenario_comparison=[{"scenario": "baseline"}],
        )
        assert pkg.as_of_date == "2025-12-31"
        assert pkg.institution_id == "BANK001"
        assert pkg.submission_period == "2025Q4"
        assert len(pkg.schedule_h1) == 1
        assert len(pkg.ccar_projection) == 1
        assert len(pkg.scenario_comparison) == 1

    def test_generated_at_auto(self) -> None:
        pkg = RegulatoryPackage(
            as_of_date="2025-12-31",
            institution_id="T1",
            submission_period="2025Q4",
            schedule_h1=[],
            ccar_projection=[],
            scenario_comparison=[],
        )
        assert isinstance(pkg.generated_at, str)
        assert len(pkg.generated_at) > 10  # non-empty ISO string

    def test_generated_at_custom(self) -> None:
        ts = "2025-12-31T00:00:00+00:00"
        pkg = RegulatoryPackage(
            as_of_date="2025-12-31",
            institution_id="T1",
            submission_period="2025Q4",
            schedule_h1=[],
            ccar_projection=[],
            scenario_comparison=[],
            generated_at=ts,
        )
        assert pkg.generated_at == ts


# ---------------------------------------------------------------------------
# TestScheduleH1Builder
# ---------------------------------------------------------------------------


class TestScheduleH1Builder:
    def test_returns_list_of_dicts(self) -> None:
        rows = ScheduleH1Builder(_make_cecl()).build()
        assert isinstance(rows, list)
        assert all(isinstance(r, dict) for r in rows)

    def test_required_keys(self) -> None:
        rows = ScheduleH1Builder(_make_cecl()).build()
        required = {"segment", "ead", "pd", "lgd", "ecl", "acl_coverage_pct"}
        for row in rows:
            assert required <= set(row.keys())

    def test_acl_coverage_calculation(self) -> None:
        seg = {"Retail": {"ead": 10000.0, "pd": 0.04, "lgd": 0.45, "ecl": 400.0}}
        rows = ScheduleH1Builder(_make_cecl(segments=seg)).build()
        retail = next(r for r in rows if r["segment"] == "Retail")
        expected = 400.0 / 10000.0 * 100.0
        assert abs(retail["acl_coverage_pct"] - expected) < 1e-9

    def test_zero_ead_no_div_error(self) -> None:
        seg = {"ZeroEAD": {"ead": 0.0, "pd": 0.1, "lgd": 0.5, "ecl": 0.0}}
        rows = ScheduleH1Builder(_make_cecl(segments=seg)).build()
        assert rows[0]["acl_coverage_pct"] == 0.0

    def test_row_count_matches_segments(self) -> None:
        seg = {
            "A": {"ead": 1000.0, "pd": 0.01, "lgd": 0.4, "ecl": 4.0},
            "B": {"ead": 2000.0, "pd": 0.02, "lgd": 0.4, "ecl": 16.0},
            "C": {"ead": 3000.0, "pd": 0.03, "lgd": 0.4, "ecl": 36.0},
        }
        rows = ScheduleH1Builder(_make_cecl(segments=seg)).build()
        assert len(rows) == 3


# ---------------------------------------------------------------------------
# TestCCARProjectionBuilder
# ---------------------------------------------------------------------------


class TestCCARProjectionBuilder:
    def test_always_9_quarters(self) -> None:
        rows = CCARProjectionBuilder(_make_stress()).build()
        assert len(rows) == 9

    def test_quarter_labels(self) -> None:
        rows = CCARProjectionBuilder(_make_stress()).build()
        labels = [r["quarter"] for r in rows]
        assert labels == [f"Q{i}" for i in range(1, 10)]

    def test_ci_keys(self) -> None:
        rows = CCARProjectionBuilder(_make_stress()).build()
        for row in rows:
            assert {"quarter", "ecl", "ci_lower", "ci_upper"} <= set(row.keys())

    def test_truncates_to_9(self) -> None:
        stub = _make_stress(quarterly_ecl=[float(i) for i in range(1, 13)])
        rows = CCARProjectionBuilder(stub).build()
        assert len(rows) == 9
        assert rows[0]["ecl"] == 1.0
        assert rows[8]["ecl"] == 9.0

    def test_extrapolates_to_9(self) -> None:
        stub = _make_stress(
            quarterly_ecl=[10.0, 20.0, 30.0, 40.0, 50.0],
            confidence_intervals=[(9.0, 11.0)] * 5,
        )
        rows = CCARProjectionBuilder(stub).build()
        assert len(rows) == 9
        # Last 4 rows should repeat the 5th value
        assert rows[5]["ecl"] == 50.0
        assert rows[8]["ecl"] == 50.0


# ---------------------------------------------------------------------------
# TestScenarioComparisonTable
# ---------------------------------------------------------------------------


class TestScenarioComparisonTable:
    def _three_scenarios(self):
        return {
            "baseline": _make_stress([50.0] * 9),
            "adverse": _make_stress([100.0] * 9),
            "severely_adverse": _make_stress([200.0] * 9),
        }

    def test_returns_list(self) -> None:
        rows = ScenarioComparisonTable(self._three_scenarios()).build()
        assert isinstance(rows, list)
        for r in rows:
            assert isinstance(r, dict)

    def test_scenario_keys(self) -> None:
        rows = ScenarioComparisonTable(self._three_scenarios()).build()
        required = {"scenario", "total_ecl", "peak_quarterly_ecl", "avg_quarterly_ecl", "quarters"}
        for row in rows:
            assert required <= set(row.keys())

    def test_row_per_scenario(self) -> None:
        rows = ScenarioComparisonTable(self._three_scenarios()).build()
        assert len(rows) == 3
        names = {r["scenario"] for r in rows}
        assert names == {"baseline", "adverse", "severely_adverse"}

    def test_peak_ecl_correct(self) -> None:
        reports = {
            "adverse": _make_stress([10.0, 20.0, 50.0, 30.0, 10.0, 5.0, 5.0, 5.0, 5.0]),
        }
        rows = ScenarioComparisonTable(reports).build()
        adv = next(r for r in rows if r["scenario"] == "adverse")
        assert adv["peak_quarterly_ecl"] == 50.0


# ---------------------------------------------------------------------------
# TestRegulatoryExporter
# ---------------------------------------------------------------------------


class TestRegulatoryExporter:
    _sample = [
        {"segment": "Retail", "ead": 10000.0, "ecl": 400.0},
        {"segment": "Corp", "ead": 20000.0, "ecl": 1200.0},
    ]

    def test_to_csv_has_header(self) -> None:
        csv_text = RegulatoryExporter.to_csv(self._sample)
        first_line = csv_text.splitlines()[0]
        assert "segment" in first_line
        assert "ead" in first_line
        assert "ecl" in first_line

    def test_to_json_is_valid(self) -> None:
        json_text = RegulatoryExporter.to_json(self._sample)
        parsed = json.loads(json_text)
        assert isinstance(parsed, list)
        assert len(parsed) == 2

    def test_to_excel_dict_column_oriented(self) -> None:
        result = RegulatoryExporter.to_excel_dict(self._sample)
        assert isinstance(result, dict)
        assert "segment" in result
        assert isinstance(result["segment"], list)
        # All columns have the same length
        lengths = [len(v) for v in result.values()]
        assert len(set(lengths)) == 1

    def test_empty_rows(self) -> None:
        assert RegulatoryExporter.to_csv([]) == ""
        assert json.loads(RegulatoryExporter.to_json([])) == []
        assert RegulatoryExporter.to_excel_dict([]) == {}


# ---------------------------------------------------------------------------
# TestBuildRegulatoryPackage
# ---------------------------------------------------------------------------


class TestBuildRegulatoryPackage:
    def _build(self):
        cecl = _make_cecl()
        stress = {
            "baseline": _make_stress([50.0] * 9),
            "adverse": _make_stress([100.0] * 9),
            "severely_adverse": _make_stress([200.0] * 9),
        }
        return build_regulatory_package(
            cecl_result=cecl,
            stress_reports=stress,
            as_of_date="2025-12-31",
            institution_id="TEST001",
            submission_period="2025Q4",
        )

    def test_returns_package(self) -> None:
        pkg = self._build()
        assert isinstance(pkg, RegulatoryPackage)

    def test_schedule_h1_populated(self) -> None:
        pkg = self._build()
        assert isinstance(pkg.schedule_h1, list)
        assert len(pkg.schedule_h1) > 0

    def test_ccar_projection_9_quarters(self) -> None:
        pkg = self._build()
        assert len(pkg.ccar_projection) == 9
