"""Tests for the 19-variable credit ontology."""

from __future__ import annotations

from orxaq.credit.ontology import (
    CREDIT_VARIABLES,
    build_credit_ontology,
    get_macro_variables,
    get_regulatory_variables,
)
from orxaq.kernel import CausalRole, VariableType


class TestBuildCreditOntology:
    def test_returns_world_dag(self):
        dag = build_credit_ontology()
        assert dag.node_count() == 19

    def test_has_expected_edge_count(self):
        dag = build_credit_ontology()
        assert dag.edge_count() == 17

    def test_no_cycles(self):
        dag = build_credit_ontology()
        order = dag.topological_sort()
        assert len(order) == 19

    def test_topological_order_macro_before_performance(self):
        dag = build_credit_ontology()
        order = dag.topological_sort()
        unemp_idx = order.index("unemployment_rate")
        default_idx = order.index("default_flag")
        assert unemp_idx < default_idx

    def test_all_entities_have_ids(self):
        dag = build_credit_ontology()
        for entity_id in CREDIT_VARIABLES:
            entity = dag.get_entity(entity_id)
            assert entity is not None, f"Missing entity: {entity_id}"

    def test_macro_entities_are_exogenous(self):
        dag = build_credit_ontology()
        macro_ids = ["unemployment_rate", "fed_funds_rate", "hpi", "gdp_growth", "cpi"]
        for eid in macro_ids:
            entity = dag.get_entity(eid)
            assert entity.causal_role == CausalRole.EXOGENOUS
            assert entity.variable_type == VariableType.MACRO

    def test_default_flag_is_outcome(self):
        dag = build_credit_ontology()
        entity = dag.get_entity("default_flag")
        assert entity.causal_role == CausalRole.OUTCOME

    def test_d_separation_unemployment_default_given_mediators(self):
        """Unemployment should be d-separated from default given income and credit_score."""
        dag = build_credit_ontology()
        conditioned = frozenset({"income", "credit_score", "dpd_30", "dpd_60", "dpd_90"})
        result = dag.d_separated("unemployment_rate", "default_flag", conditioned)
        assert result is True

    def test_not_d_separated_without_conditioning(self):
        """Unemployment should NOT be d-separated from default with empty conditioning set."""
        dag = build_credit_ontology()
        result = dag.d_separated("unemployment_rate", "default_flag", frozenset())
        assert result is False

    def test_entity_bounds(self):
        dag = build_credit_ontology()
        credit_score = dag.get_entity("credit_score")
        assert credit_score.bounds == (300.0, 850.0)

        unemp = dag.get_entity("unemployment_rate")
        assert unemp.bounds == (0.0, 0.30)


class TestRegulatoryVariables:
    def test_ccar_variables(self):
        ccar = get_regulatory_variables("CCAR")
        ccar_ids = {e.entity_id for e in ccar}
        assert "unemployment_rate" in ccar_ids
        assert "gdp_growth" in ccar_ids
        assert "default_flag" in ccar_ids

    def test_cecl_variables(self):
        cecl = get_regulatory_variables("CECL")
        cecl_ids = {e.entity_id for e in cecl}
        assert "dpd_30" in cecl_ids
        assert "default_flag" in cecl_ids
        assert "credit_score" in cecl_ids

    def test_cfpb_variables(self):
        cfpb = get_regulatory_variables("CFPB")
        cfpb_ids = {e.entity_id for e in cfpb}
        assert "age" in cfpb_ids
        assert "credit_score" in cfpb_ids

    def test_macro_variables(self):
        macros = get_macro_variables()
        assert len(macros) == 5
        assert all(e.domain == "macro" for e in macros)


class TestCreditVariables:
    def test_has_19_entries(self):
        assert len(CREDIT_VARIABLES) == 19

    def test_all_ids_are_strings(self):
        for key, val in CREDIT_VARIABLES.items():
            assert isinstance(key, str)
            assert isinstance(val, str)
