"""Tests for the CCAR/DFAST scenario engine."""

from __future__ import annotations

import pytest

from orxaq.credit.causal_pd import CausalPDModel
from orxaq.credit.ontology import build_credit_ontology
from orxaq.credit.scenarios import ADVERSE, BASELINE, SEVERELY_ADVERSE, ScenarioEngine
from orxaq.credit.synthetic import CreditDataGenerator


@pytest.fixture()
def scenario_setup():
    """Set up scenario engine with fitted PD model."""
    dag = build_credit_ontology()
    gen = CreditDataGenerator(dag, seed=42)
    data = gen.generate(500)
    pd_model = CausalPDModel(dag)
    pd_model.fit(data)
    engine = ScenarioEngine(dag)
    return engine, pd_model, data


class TestScenarioDefinitions:
    def test_baseline_defined(self):
        assert "unemployment_rate" in BASELINE
        assert "fed_funds_rate" in BASELINE
        assert "gdp_growth" in BASELINE
        assert "hpi" in BASELINE
        assert "cpi" in BASELINE

    def test_adverse_defined(self):
        assert len(ADVERSE) == 5
        assert ADVERSE["unemployment_rate"] > BASELINE["unemployment_rate"]

    def test_severely_adverse_defined(self):
        assert len(SEVERELY_ADVERSE) == 5
        assert SEVERELY_ADVERSE["unemployment_rate"] > ADVERSE["unemployment_rate"]

    def test_severely_adverse_worst(self):
        assert SEVERELY_ADVERSE["unemployment_rate"] == 0.10
        assert SEVERELY_ADVERSE["gdp_growth"] == -0.04
        assert SEVERELY_ADVERSE["hpi"] < BASELINE["hpi"]


class TestScenarioEngine:
    def test_apply_scenario_preserves_count(self, scenario_setup):
        engine, _, data = scenario_setup
        stressed = engine.apply_scenario(SEVERELY_ADVERSE, data[:50])
        assert len(stressed) == 50

    def test_apply_scenario_sets_macro_values(self, scenario_setup):
        engine, _, data = scenario_setup
        stressed = engine.apply_scenario(SEVERELY_ADVERSE, data[:50])
        for row in stressed:
            assert row["unemployment_rate"] == 0.10
            assert row["fed_funds_rate"] == 0.005
            assert row["gdp_growth"] == -0.04

    def test_severely_adverse_higher_pd_than_baseline(self, scenario_setup):
        engine, pd_model, data = scenario_setup
        portfolio = data[:100]

        pd_baseline = engine.stress_pd(pd_model, BASELINE, portfolio)
        pd_severe = engine.stress_pd(pd_model, SEVERELY_ADVERSE, portfolio)

        # Severely adverse should produce higher or equal portfolio PD
        # (equal when model saturates at 0.0 or 1.0 for both scenarios)
        assert pd_severe >= pd_baseline

    def test_adverse_between_baseline_and_severe(self, scenario_setup):
        engine, pd_model, data = scenario_setup
        portfolio = data[:100]

        pd_baseline = engine.stress_pd(pd_model, BASELINE, portfolio)
        pd_adverse = engine.stress_pd(pd_model, ADVERSE, portfolio)
        pd_severe = engine.stress_pd(pd_model, SEVERELY_ADVERSE, portfolio)

        # Ordering: baseline < adverse < severely adverse
        assert pd_baseline <= pd_adverse <= pd_severe

    def test_stress_pd_not_fitted_raises(self):
        dag = build_credit_ontology()
        pd_model = CausalPDModel(dag)
        engine = ScenarioEngine(dag)
        with pytest.raises(RuntimeError, match="not fitted"):
            engine.stress_pd(pd_model, BASELINE, [{"credit_score": 700}])

    def test_apply_empty_portfolio(self, scenario_setup):
        engine, _, _ = scenario_setup
        result = engine.apply_scenario(BASELINE, [])
        assert result == []


# ── Targeted coverage (line 86: empty portfolio → 0.0) ──────


class TestStressPdEmptyPortfolio:
    """Cover line 86: stress_pd with empty portfolio returns 0.0."""

    def test_empty_portfolio_returns_zero(self, scenario_setup):
        engine, pd_model, _ = scenario_setup
        result = engine.stress_pd(pd_model, BASELINE, [])
        assert result == 0.0
