"""
tests/test_cascade_feedback.py — Phase 4: Cascade Feedback (Channel 4) tests.

Validates:
  - SessionLedger.get_anomaly_history() aggregates anomalies
  - SessionLedger.get_cascade_status() factors anomaly count
  - SessionLedger.get_cascade_forward() produces rich envelope
  - build_framework_context() with cascade_forward parameter
  - Forward directives contain enforcement language
  - Anomaly forwarding is not suppressed
"""

from __future__ import annotations

import pytest

from fw_server.session import SessionLedger, get_ledger, reset_ledger
from fw_server.context import build_framework_context


@pytest.fixture(autouse=True)
def _reset():
    reset_ledger()
    yield
    reset_ledger()


# =====================================================================
# A. Anomaly History
# =====================================================================

class TestAnomalyHistory:
    """Test SessionLedger.get_anomaly_history()."""

    def test_empty_session(self):
        ledger = get_ledger()
        assert ledger.get_anomaly_history() == []

    def test_single_call_no_anomalies(self):
        ledger = get_ledger()
        ledger.record_call("dusun", grade="Tasdik", gate="PASS")
        assert ledger.get_anomaly_history() == []

    def test_single_call_with_anomalies(self):
        ledger = get_ledger()
        ledger.record_call(
            "validate_stage",
            grade="Tasdik",
            gate="WARN",
            anomalies=["missing coverage", "low composite"],
        )
        history = ledger.get_anomaly_history()
        assert len(history) == 2
        assert "missing coverage" in history
        assert "low composite" in history

    def test_multiple_calls_accumulate(self):
        ledger = get_ledger()
        ledger.record_call(
            "validate_stage",
            grade="Tasdik",
            gate="WARN",
            anomalies=["anomaly-A"],
        )
        ledger.record_call(
            "dusun",
            grade="Tasdik",
            gate="PASS",
            anomalies=["anomaly-B", "anomaly-C"],
        )
        history = ledger.get_anomaly_history()
        assert len(history) == 3
        assert set(history) == {"anomaly-A", "anomaly-B", "anomaly-C"}

    def test_included_in_cumulative_context(self):
        ledger = get_ledger()
        ledger.record_call(
            "validate_stage",
            grade="Tasdik",
            gate="PASS",
            anomalies=["anom-1"],
        )
        ctx = ledger.get_cumulative_context()
        assert "anomaly_history" in ctx
        assert ctx["anomaly_history"] == ["anom-1"]


# =====================================================================
# B. Cascade Status — anomaly-based degradation
# =====================================================================

class TestCascadeStatusAnomaly:
    """Test that high anomaly count degrades cascade status."""

    def test_no_anomalies_stays_nominal(self):
        ledger = get_ledger()
        ledger.record_call("dusun", grade="Tasdik", gate="PASS")
        status = ledger.get_cascade_status()
        assert status["degradation_level"] == "NOMINAL"

    def test_two_anomalies_still_nominal(self):
        """Under threshold (3), stays NOMINAL."""
        ledger = get_ledger()
        ledger.record_call(
            "validate_stage",
            grade="Tasdik",
            gate="PASS",
            anomalies=["a1", "a2"],
        )
        status = ledger.get_cascade_status()
        assert status["degradation_level"] == "NOMINAL"

    def test_three_anomalies_degrades(self):
        """At threshold (3), becomes DEGRADED."""
        ledger = get_ledger()
        ledger.record_call(
            "validate_stage",
            grade="Tasdik",
            gate="PASS",
            anomalies=["a1", "a2", "a3"],
        )
        status = ledger.get_cascade_status()
        assert status["degradation_level"] == "DEGRADED"
        assert "3 anomalies" in status["reason"]

    def test_anomalies_across_calls_accumulate(self):
        """Anomalies spread across calls still trigger threshold."""
        ledger = get_ledger()
        ledger.record_call(
            "validate_stage", grade="Tasdik", gate="PASS", anomalies=["a1"],
        )
        ledger.record_call(
            "dusun", grade="Tasdik", gate="PASS", anomalies=["a2", "a3"],
        )
        status = ledger.get_cascade_status()
        assert status["degradation_level"] == "DEGRADED"

    def test_fail_gate_overrides_anomaly_count(self):
        """FAIL gate = CRITICAL regardless of anomaly count."""
        ledger = get_ledger()
        ledger.record_call(
            "validate_stage", grade="Tasdik", gate="FAIL", anomalies=["a1"],
        )
        status = ledger.get_cascade_status()
        assert status["degradation_level"] == "CRITICAL"


# =====================================================================
# C. Cascade Forward
# =====================================================================

class TestCascadeForward:
    """Test SessionLedger.get_cascade_forward()."""

    def test_empty_session_returns_nominal(self):
        ledger = get_ledger()
        fwd = ledger.get_cascade_forward()
        assert fwd["degradation_level"] == "NOMINAL"
        assert fwd["prior_anomalies"] == []
        assert fwd["prior_anomaly_count"] == 0
        assert fwd["forward_directives"] == []

    def test_required_keys_present(self):
        ledger = get_ledger()
        fwd = ledger.get_cascade_forward()
        for key in [
            "degradation_level",
            "reason",
            "prior_anomalies",
            "prior_anomaly_count",
            "prior_gate_summary",
            "prior_kavaid_failures",
            "forward_directives",
        ]:
            assert key in fwd, f"Missing key: {key}"

    def test_critical_contains_must_directives(self):
        ledger = get_ledger()
        ledger.record_call("validate_stage", grade="Tasdik", gate="FAIL")
        fwd = ledger.get_cascade_forward()
        assert fwd["degradation_level"] == "CRITICAL"
        directives_text = " ".join(fwd["forward_directives"])
        assert "MUST" in directives_text
        assert "NEVER" in directives_text

    def test_degraded_contains_must_directives(self):
        ledger = get_ledger()
        ledger.record_call("validate_stage", grade="Tasdik", gate="WARN")
        fwd = ledger.get_cascade_forward()
        assert fwd["degradation_level"] == "DEGRADED"
        directives_text = " ".join(fwd["forward_directives"])
        assert "MUST" in directives_text

    def test_anomalies_forwarded(self):
        ledger = get_ledger()
        ledger.record_call(
            "validate_stage",
            grade="Tasdik",
            gate="WARN",
            anomalies=["gap-A", "gap-B"],
        )
        fwd = ledger.get_cascade_forward()
        assert fwd["prior_anomaly_count"] == 2
        assert "gap-A" in fwd["prior_anomalies"]
        # Forward directives should mention the anomalies
        directives_text = " ".join(fwd["forward_directives"])
        assert "anomalies" in directives_text.lower() or "gap-A" in directives_text

    def test_kavaid_failures_forwarded(self):
        ledger = get_ledger()
        ledger.record_call("check_kavaid", grade="Tasdik", gate="PASS")
        ledger.record_kavaid_failure("KV_4")
        fwd = ledger.get_cascade_forward()
        assert "KV_4" in fwd["prior_kavaid_failures"]
        directives_text = " ".join(fwd["forward_directives"])
        assert "KV_4" in directives_text

    def test_grade_ceiling_forwarded(self):
        ledger = get_ledger()
        ledger.record_call("dusun", grade="Tasavvur", gate="PASS")
        fwd = ledger.get_cascade_forward()
        directives_text = " ".join(fwd["forward_directives"])
        assert "Tasavvur" in directives_text
        assert "MUST NOT" in directives_text

    def test_gate_history_forwarded(self):
        ledger = get_ledger()
        ledger.record_call("validate_stage", grade="Tasdik", gate="PASS")
        ledger.record_call("validate_stage", grade="Tasdik", gate="WARN")
        fwd = ledger.get_cascade_forward()
        assert fwd["prior_gate_summary"] == ["PASS", "WARN"]

    def test_nominal_no_directives(self):
        """When everything is fine, forward directives should be minimal."""
        ledger = get_ledger()
        ledger.record_call("dusun", grade="İlmelyakîn", gate="PASS")
        fwd = ledger.get_cascade_forward()
        assert fwd["degradation_level"] == "NOMINAL"
        # May have grade ceiling directive only
        assert len(fwd["forward_directives"]) <= 1

    def test_many_anomalies_truncated_in_directive(self):
        """More than 5 anomalies should show '...' in directive text."""
        ledger = get_ledger()
        ledger.record_call(
            "validate_stage",
            grade="Tasdik",
            gate="PASS",
            anomalies=[f"anom-{i}" for i in range(7)],
        )
        fwd = ledger.get_cascade_forward()
        directives_text = " ".join(fwd["forward_directives"])
        assert "..." in directives_text


# =====================================================================
# D. Context Builder — cascade_forward integration
# =====================================================================

class TestContextCascadeForward:
    """Test build_framework_context() with cascade_forward parameter."""

    def test_cascade_forward_included_when_provided(self):
        cascade_fwd = {
            "degradation_level": "DEGRADED",
            "reason": "WARN gate",
            "prior_anomalies": ["gap-X"],
            "prior_anomaly_count": 1,
            "prior_gate_summary": ["WARN"],
            "prior_kavaid_failures": [],
            "forward_directives": ["You MUST acknowledge prior WARN."],
        }
        ctx = build_framework_context(
            "validate_stage",
            cascade_forward=cascade_fwd,
        )
        assert "cascade_forward" in ctx
        assert ctx["cascade_forward"]["degradation_level"] == "DEGRADED"

    def test_forward_directives_extracted(self):
        cascade_fwd = {
            "degradation_level": "CRITICAL",
            "reason": "FAIL gate",
            "prior_anomalies": [],
            "prior_anomaly_count": 0,
            "prior_gate_summary": ["FAIL"],
            "prior_kavaid_failures": [],
            "forward_directives": [
                "CRITICAL cascade: a FAIL gate occurred.",
                "You MUST NOT proceed."
            ],
        }
        ctx = build_framework_context(
            "dusun",
            cascade_forward=cascade_fwd,
        )
        assert "cascade_forward_directives" in ctx
        assert len(ctx["cascade_forward_directives"]) == 2

    def test_no_cascade_forward_when_none(self):
        ctx = build_framework_context("dusun")
        assert "cascade_forward" not in ctx
        assert "cascade_forward_directives" not in ctx

    def test_empty_forward_directives_no_key(self):
        cascade_fwd = {
            "degradation_level": "NOMINAL",
            "reason": "no prior calls",
            "prior_anomalies": [],
            "prior_anomaly_count": 0,
            "prior_gate_summary": [],
            "prior_kavaid_failures": [],
            "forward_directives": [],
        }
        ctx = build_framework_context(
            "dusun",
            cascade_forward=cascade_fwd,
        )
        assert "cascade_forward" in ctx
        assert "cascade_forward_directives" not in ctx

    def test_cascade_forward_coexists_with_cascade_status(self):
        """Both cascade_status and cascade_forward can coexist."""
        status = {"degradation_level": "DEGRADED", "reason": "WARN"}
        forward = {
            "degradation_level": "DEGRADED",
            "reason": "WARN gate",
            "prior_anomalies": [],
            "prior_anomaly_count": 0,
            "prior_gate_summary": ["WARN"],
            "prior_kavaid_failures": [],
            "forward_directives": ["Acknowledge WARN."],
        }
        ctx = build_framework_context(
            "validate_stage",
            cascade_status=status,
            cascade_forward=forward,
        )
        assert "cascade" in ctx
        assert "cascade_forward" in ctx
        assert "cascade_directive" in ctx  # from cascade_status DEGRADED
        assert "cascade_forward_directives" in ctx  # from forward


# =====================================================================
# E. Enforcement language checks
# =====================================================================

class TestCascadeEnforcementLanguage:
    """Verify forward directives use MUST/MUST NOT/NEVER."""

    def _get_all_directives(self, level: str) -> str:
        ledger = get_ledger()
        if level == "CRITICAL":
            ledger.record_call("validate_stage", grade="Tasdik", gate="FAIL",
                               anomalies=["x"])
        elif level == "DEGRADED":
            ledger.record_call("validate_stage", grade="Tasdik", gate="WARN",
                               anomalies=["x"])
        else:
            ledger.record_call("dusun", grade="İlmelyakîn", gate="PASS")
        fwd = ledger.get_cascade_forward()
        return " ".join(fwd["forward_directives"])

    def test_critical_enforcement(self):
        text = self._get_all_directives("CRITICAL")
        assert "MUST" in text
        assert "NEVER" in text

    def test_degraded_enforcement(self):
        text = self._get_all_directives("DEGRADED")
        assert "MUST" in text

    def test_nominal_no_enforcement_needed(self):
        text = self._get_all_directives("NOMINAL")
        # May have grade ceiling directive, but no CRITICAL/DEGRADED enforcement
        # Just assert no crash — nominal is permissive
        assert isinstance(text, str)


# =====================================================================
# F. KV₃ Compliance — errors swallowed
# =====================================================================

class TestCascadeKV3:
    """get_cascade_forward() must never raise."""

    def test_cascade_forward_never_raises(self):
        """Even with corrupted internal state, get_cascade_forward swallows."""
        ledger = get_ledger()
        # Corrupt internal state
        ledger._calls = None  # type: ignore[assignment]
        fwd = ledger.get_cascade_forward()
        assert fwd["degradation_level"] == "NOMINAL"
        assert fwd["forward_directives"] == []
