"""Tests for the CECL lifetime loss engine."""

from __future__ import annotations

import pytest

from orxaq.credit.causal_pd import CausalPDModel
from orxaq.credit.cecl import CECLEngine, ECLResult
from orxaq.credit.ontology import build_credit_ontology
from orxaq.credit.synthetic import CreditDataGenerator


@pytest.fixture()
def cecl_setup():
    """Set up CECL 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 = CECLEngine(pd_model, dag)
    return engine, data, dag


class TestCECLEngine:
    def test_ecl_is_positive(self, cecl_setup):
        engine, data, _ = cecl_setup
        result = engine.expected_credit_loss(data[:50])
        assert result.total_ecl >= 0.0

    def test_ecl_result_type(self, cecl_setup):
        engine, data, _ = cecl_setup
        result = engine.expected_credit_loss(data[:50])
        assert isinstance(result, ECLResult)

    def test_ecl_has_segments(self, cecl_setup):
        engine, data, _ = cecl_setup
        result = engine.expected_credit_loss(data[:50])
        assert isinstance(result.ecl_by_segment, dict)

    def test_confidence_band_contains_estimate(self, cecl_setup):
        engine, data, _ = cecl_setup
        result = engine.expected_credit_loss(data[:50])
        low, high = result.confidence_band
        assert low <= result.total_ecl <= high

    def test_dynamic_horizon_returns_valid_range(self, cecl_setup):
        engine, _, _ = cecl_setup
        horizon = engine.dynamic_horizon()
        assert horizon in (4, 8, 12)

    def test_fixed_horizon_override(self, cecl_setup):
        engine, data, _ = cecl_setup
        result = engine.expected_credit_loss(data[:50], horizon_quarters=6)
        assert result.horizon_quarters == 6
        assert result.horizon_method == "fixed"

    def test_dynamic_horizon_method(self, cecl_setup):
        engine, data, _ = cecl_setup
        result = engine.expected_credit_loss(data[:50])
        assert result.horizon_method == "soliton_dynamic"

    def test_longer_horizon_higher_ecl(self, cecl_setup):
        engine, data, _ = cecl_setup
        portfolio = data[:50]
        ecl_4 = engine.expected_credit_loss(portfolio, horizon_quarters=4)
        ecl_12 = engine.expected_credit_loss(portfolio, horizon_quarters=12)
        # Longer horizon should produce higher ECL (more time for defaults)
        assert ecl_12.total_ecl >= ecl_4.total_ecl

    def test_empty_portfolio(self, cecl_setup):
        engine, _, _ = cecl_setup
        result = engine.expected_credit_loss([])
        assert result.total_ecl == 0.0
        assert result.horizon_quarters == 0

    def test_ecl_result_fields(self, cecl_setup):
        engine, data, _ = cecl_setup
        result = engine.expected_credit_loss(data[:50])
        assert result.lgd == 0.45
        assert result.pd_mean >= 0.0
        assert result.ead_total >= 0.0

    def test_not_fitted_raises(self):
        dag = build_credit_ontology()
        pd_model = CausalPDModel(dag)
        engine = CECLEngine(pd_model, dag)
        with pytest.raises(RuntimeError, match="not fitted"):
            engine.expected_credit_loss([{"credit_score": 700}])


# ── Targeted coverage (lines 150, 152, 170) ─────────────────


class TestCECLHorizonBranches:
    """Cover lines 150, 152: soliton-based horizon returns."""

    def test_high_soliton_12_quarters(self, cecl_setup):
        """Cover line 150: avg_score >= HIGH → 12 quarters."""
        from unittest.mock import patch

        engine, _, _ = cecl_setup
        with patch.object(engine, "_average_soliton_score", return_value=0.85):
            h = engine.dynamic_horizon()
        assert h == 12

    def test_medium_soliton_8_quarters(self, cecl_setup):
        """Cover line 152: avg_score >= MEDIUM → 8 quarters."""
        from unittest.mock import patch

        engine, _, _ = cecl_setup
        with patch.object(engine, "_average_soliton_score", return_value=0.70):
            h = engine.dynamic_horizon()
        assert h == 8

    def test_low_soliton_4_quarters(self, cecl_setup):
        """Cover line 153: avg_score < MEDIUM → 4 quarters."""
        from unittest.mock import patch

        engine, _, _ = cecl_setup
        with patch.object(engine, "_average_soliton_score", return_value=0.40):
            h = engine.dynamic_horizon()
        assert h == 4

    def test_no_edge_scores_default(self, cecl_setup):
        """Cover line 170: paths exist but edges have no scores."""
        from unittest.mock import patch

        engine, _, _ = cecl_setup
        with patch.object(engine, "_get_edges", return_value=[]):
            score = engine._average_soliton_score()
        assert score == 0.5
