"""Tests for fair lending causal analysis."""

from __future__ import annotations

from orxaq.credit.fair_lending import (
    LEGITIMATE_MEDIATORS,
    PROTECTED_CHARACTERISTICS,
    EffectDecomposition,
    FairLendingAnalyzer,
    FairnessReport,
)
from orxaq.credit.ontology import build_credit_ontology


class TestFairLendingAnalyzer:
    def setup_method(self):
        self.dag = build_credit_ontology()
        self.analyzer = FairLendingAnalyzer(self.dag)

    def test_decompose_effect_returns_decomposition(self):
        result = self.analyzer.decompose_effect("age", "default_flag")
        assert isinstance(result, EffectDecomposition)

    def test_decompose_finds_paths(self):
        result = self.analyzer.decompose_effect("age", "default_flag")
        total_paths = len(result.legitimate_paths) + len(result.concerning_paths)
        assert total_paths > 0

    def test_age_has_indirect_through_employment(self):
        """Age → employment_length → dpd_30 → ... → default should be legitimate."""
        result = self.analyzer.decompose_effect("age", "default_flag")
        # At least one path should go through employment_length
        all_paths = result.legitimate_paths + result.concerning_paths
        has_employment_path = any("employment_length" in path for path in all_paths)
        assert has_employment_path

    def test_legitimate_paths_use_legitimate_mediators(self):
        result = self.analyzer.decompose_effect("age", "default_flag")
        for path in result.legitimate_paths:
            mediators = set(path[1:-1])
            assert mediators <= LEGITIMATE_MEDIATORS

    def test_direct_effect_between_0_and_1(self):
        result = self.analyzer.decompose_effect("age", "default_flag")
        assert 0.0 <= result.direct_effect <= 1.0

    def test_indirect_effects_dict(self):
        result = self.analyzer.decompose_effect("age", "default_flag")
        assert isinstance(result.indirect_effects, dict)


class TestFairnessReport:
    def setup_method(self):
        self.dag = build_credit_ontology()
        self.analyzer = FairLendingAnalyzer(self.dag)

    def test_audit_decision_returns_report(self):
        borrower = {
            "age": 35.0,
            "credit_score": 720.0,
            "default_flag": 0.0,
        }
        report = self.analyzer.audit_decision(borrower)
        assert isinstance(report, FairnessReport)

    def test_audit_approves_low_risk(self):
        borrower = {
            "age": 35.0,
            "default_flag": 0.0,
        }
        report = self.analyzer.audit_decision(borrower)
        assert report.decision == "approve"

    def test_audit_denies_high_risk(self):
        borrower = {
            "age": 35.0,
            "default_flag": 1.0,
        }
        report = self.analyzer.audit_decision(borrower)
        assert report.decision == "deny"

    def test_recommendation_values(self):
        borrower = {
            "age": 35.0,
            "default_flag": 0.0,
        }
        report = self.analyzer.audit_decision(borrower)
        assert report.recommendation in ("clear", "review", "flag")

    def test_no_protected_char(self):
        """Borrower without protected characteristics should get clear report."""
        borrower = {
            "credit_score": 720.0,
            "default_flag": 0.0,
        }
        report = self.analyzer.audit_decision(borrower)
        assert report.recommendation == "clear"
        assert report.protected_var == "none"

    def test_report_has_decomposition(self):
        borrower = {
            "age": 35.0,
            "default_flag": 0.0,
        }
        report = self.analyzer.audit_decision(borrower)
        assert report.decomposition is not None


class TestDecomposeEffectPathClassification:
    """Cover lines 100-106: direct, legitimate, and concerning path logic."""

    def setup_method(self):
        self.dag = build_credit_ontology()
        self.analyzer = FairLendingAnalyzer(self.dag)

    def test_legitimate_paths_have_indirect_effects(self):
        """Lines 103-106: paths through only legitimate mediators."""
        result = self.analyzer.decompose_effect("age", "default_flag")
        # If there are legitimate paths, indirect_effects should be populated.
        if result.legitimate_paths:
            assert len(result.indirect_effects) > 0
            for mediator in result.indirect_effects:
                assert mediator in LEGITIMATE_MEDIATORS

    def test_concerning_includes_direct_paths(self):
        """Line 100: direct path (no mediators) classified as concerning."""
        result = self.analyzer.decompose_effect("age", "default_flag")
        # At least one type of concerning path should exist.
        assert len(result.concerning_paths) > 0 or len(result.legitimate_paths) > 0

    def test_custom_dag_with_legitimate_mediator(self):
        """Force a legitimate mediator path to cover lines 103-106."""
        from orxaq.kernel import CausalEdge, Entity, WorldDAG

        dag = WorldDAG()
        dag.add_entity(Entity(entity_id="age", name="Age"))
        dag.add_entity(Entity(entity_id="employment_length", name="Emp"))
        dag.add_entity(Entity(entity_id="default_flag", name="Default"))
        dag.add_edge(CausalEdge(source="age", target="employment_length"))
        dag.add_edge(CausalEdge(source="employment_length", target="default_flag"))

        analyzer = FairLendingAnalyzer(dag)
        result = analyzer.decompose_effect("age", "default_flag")
        assert len(result.legitimate_paths) == 1
        assert result.indirect_effects["employment_length"] > 0

    def test_custom_dag_direct_path_only(self):
        """Direct path with no mediators → concerning (line 100)."""
        from orxaq.kernel import CausalEdge, Entity, WorldDAG

        dag = WorldDAG()
        dag.add_entity(Entity(entity_id="age", name="Age"))
        dag.add_entity(Entity(entity_id="default_flag", name="Default"))
        dag.add_edge(CausalEdge(source="age", target="default_flag"))

        analyzer = FairLendingAnalyzer(dag)
        result = analyzer.decompose_effect("age", "default_flag")
        assert len(result.concerning_paths) == 1
        assert len(result.legitimate_paths) == 0
        assert result.direct_effect == 1.0


class TestRecommendationThresholds:
    """Cover lines 179-184: flag, review, clear recommendation branches."""

    def test_flag_recommendation_high_direct_effect(self):
        """Line 179-180: direct_effect > 0.3 → flag."""
        from orxaq.kernel import CausalEdge, Entity, WorldDAG

        dag = WorldDAG()
        dag.add_entity(Entity(entity_id="age", name="Age"))
        dag.add_entity(Entity(entity_id="default_flag", name="Default"))
        dag.add_edge(CausalEdge(source="age", target="default_flag"))

        analyzer = FairLendingAnalyzer(dag)
        borrower = {"age": 35.0, "default_flag": 0.0}
        report = analyzer.audit_decision(borrower)
        assert report.recommendation == "flag"
        assert report.direct_effect_size > 0.3

    def test_review_recommendation_medium_direct_effect(self):
        """Line 181-182: direct_effect > 0.1 → review."""
        from orxaq.kernel import CausalEdge, Entity, WorldDAG

        dag = WorldDAG()
        dag.add_entity(Entity(entity_id="age", name="Age"))
        dag.add_entity(Entity(entity_id="employment_length", name="Emp"))
        dag.add_entity(Entity(entity_id="income", name="Inc"))
        dag.add_entity(Entity(entity_id="credit_score", name="CS"))
        dag.add_entity(Entity(entity_id="default_flag", name="Default"))
        dag.add_edge(CausalEdge(source="age", target="default_flag"))
        dag.add_edge(CausalEdge(source="age", target="employment_length"))
        dag.add_edge(CausalEdge(source="employment_length", target="default_flag"))
        dag.add_edge(CausalEdge(source="age", target="income"))
        dag.add_edge(CausalEdge(source="income", target="default_flag"))
        dag.add_edge(CausalEdge(source="age", target="credit_score"))
        dag.add_edge(CausalEdge(source="credit_score", target="default_flag"))

        analyzer = FairLendingAnalyzer(dag)
        borrower = {"age": 35.0, "default_flag": 0.0}
        report = analyzer.audit_decision(borrower)
        assert report.recommendation in ("review", "flag")
        assert report.direct_effect_size > 0.1

    def test_clear_recommendation_low_effect(self):
        """Line 183-184: direct_effect <= 0.1 and not changed → clear."""
        from orxaq.kernel import CausalEdge, Entity, WorldDAG

        dag = WorldDAG()
        dag.add_entity(Entity(entity_id="age", name="Age"))
        dag.add_entity(Entity(entity_id="income", name="Inc"))
        dag.add_entity(Entity(entity_id="default_flag", name="Default"))
        dag.add_edge(CausalEdge(source="age", target="income"))
        dag.add_edge(CausalEdge(source="income", target="default_flag"))

        analyzer = FairLendingAnalyzer(dag)
        borrower = {"age": 35.0, "default_flag": 0.0}
        report = analyzer.audit_decision(borrower)
        assert report.recommendation == "clear"
        assert report.direct_effect_size == 0.0


class TestConstants:
    def test_legitimate_mediators_defined(self):
        assert "employment_length" in LEGITIMATE_MEDIATORS
        assert "income" in LEGITIMATE_MEDIATORS
        assert "credit_score" in LEGITIMATE_MEDIATORS

    def test_protected_characteristics_defined(self):
        assert "age" in PROTECTED_CHARACTERISTICS
