"""Phase 6 — Hayat Bridge End-to-End Integration Tests.

Verifies that all four channels (Tool Description, Enriched Returns,
Session Tracking, Cascade Feedback) cooperate correctly across
simulated multi-tool sessions.

These tests exercise the FULL integration surface:
  SessionLedger → build_framework_context → _framework_context envelope

They do NOT start an MCP server — they directly call the same functions
that ``server.py``'s injection blocks call, reproducing the exact wiring.
"""

from __future__ import annotations

import json
import pytest

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


# ── Fixtures ────────────────────────────────────────────────────────

@pytest.fixture(autouse=True)
def _clean_session():
    """Reset the global ledger before and after every test."""
    reset_ledger()
    yield
    reset_ledger()


# ════════════════════════════════════════════════════════════════════
# 1. Single-Tool Full Channel Test
# ════════════════════════════════════════════════════════════════════

class TestSingleToolFullChannel:
    """A single tool call should produce a complete _framework_context
    envelope with all 4 channels represented."""

    def _simulate_validate_stage(
        self,
        *,
        grade: str = "Tasdik",
        gate: str = "PASS",
        composite: float = 0.72,
        kvp: int = 7,
        anomalies: list[str] | None = None,
    ) -> dict:
        """Reproduce exactly what server.py does for validate_stage."""
        anomalies = anomalies or []
        ledger = get_ledger()
        # Channel 3: record
        ledger.record_call(
            "validate_stage",
            grade=grade,
            gate=gate,
            composite_score=composite,
            kavaid_pass_count=kvp,
            anomalies=anomalies,
        )
        # Channel 2: build context
        ctx = build_framework_context(
            "validate_stage",
            grade=grade,
            gate=gate,
            composite_score=composite,
            kavaid_pass_count=kvp,
            anomalies=anomalies,
            session_context=ledger.get_cumulative_context(),
            cascade_status=ledger.get_cascade_status(),
            cascade_forward=ledger.get_cascade_forward(),
        )
        return ctx

    def test_context_has_directives(self) -> None:
        ctx = self._simulate_validate_stage()
        assert "directives" in ctx
        assert isinstance(ctx["directives"], list)
        assert len(ctx["directives"]) > 0

    def test_context_has_grade_ceiling(self) -> None:
        ctx = self._simulate_validate_stage(grade="Tasdik")
        assert ctx["grade_ceiling"] == "Tasdik"

    def test_context_has_composite(self) -> None:
        ctx = self._simulate_validate_stage(composite=0.72)
        assert ctx["composite_score"] == 0.72

    def test_context_has_gate(self) -> None:
        ctx = self._simulate_validate_stage(gate="PASS")
        assert ctx["gate_verdict"] == "PASS"

    def test_context_has_session_awareness(self) -> None:
        ctx = self._simulate_validate_stage()
        assert "session" in ctx
        assert ctx["session"]["session_call_count"] == 1

    def test_context_has_cascade_status(self) -> None:
        ctx = self._simulate_validate_stage()
        assert "cascade" in ctx
        assert ctx["cascade"]["degradation_level"] in ("NOMINAL", "DEGRADED", "CRITICAL")

    def test_context_has_cascade_forward(self) -> None:
        ctx = self._simulate_validate_stage()
        assert "cascade_forward" in ctx
        assert isinstance(ctx["cascade_forward"], dict)

    def test_no_kv4_violation_normal(self) -> None:
        ctx = self._simulate_validate_stage(composite=0.72)
        assert "kv4_violation" not in ctx

    def test_kv4_violation_flagged(self) -> None:
        ctx = self._simulate_validate_stage(composite=0.96)
        assert "kv4_violation" in ctx
        assert "0.95" in ctx["kv4_violation"]


# ════════════════════════════════════════════════════════════════════
# 2. Multi-Tool Session Awareness
# ════════════════════════════════════════════════════════════════════

class TestMultiToolSession:
    """Simulate a 3-tool pipeline: dusun → validate_stage → check_kavaid.
    Each subsequent call should see the prior calls in session context."""

    def _call_dusun(self) -> dict:
        ledger = get_ledger()
        ledger.record_call(
            "dusun",
            grade="İlmelyakîn",
            composite_score=0.82,
            kavaid_pass_count=8,
        )
        return build_framework_context(
            "dusun",
            grade="İlmelyakîn",
            composite_score=0.82,
            kavaid_pass_count=8,
            session_context=ledger.get_cumulative_context(),
            cascade_status=ledger.get_cascade_status(),
            cascade_forward=ledger.get_cascade_forward(),
        )

    def _call_validate(self) -> dict:
        ledger = get_ledger()
        ledger.record_call(
            "validate_stage",
            grade="Tasdik",
            gate="PASS",
            composite_score=0.74,
            kavaid_pass_count=7,
            anomalies=["KV₆ seed low"],
        )
        return build_framework_context(
            "validate_stage",
            grade="Tasdik",
            gate="PASS",
            composite_score=0.74,
            kavaid_pass_count=7,
            anomalies=["KV₆ seed low"],
            session_context=ledger.get_cumulative_context(),
            cascade_status=ledger.get_cascade_status(),
            cascade_forward=ledger.get_cascade_forward(),
        )

    def _call_kavaid(self) -> dict:
        ledger = get_ledger()
        ledger.record_call(
            "check_kavaid",
            composite_score=0.74,
            kavaid_pass_count=6,
        )
        ledger.record_kavaid_failure("KV₆")
        ledger.record_kavaid_failure("KV₇")
        return build_framework_context(
            "check_kavaid",
            composite_score=0.74,
            kavaid_pass_count=6,
            session_context=ledger.get_cumulative_context(),
            cascade_status=ledger.get_cascade_status(),
            cascade_forward=ledger.get_cascade_forward(),
        )

    def test_first_call_sees_one_tool(self) -> None:
        ctx1 = self._call_dusun()
        assert ctx1["session"]["session_call_count"] == 1
        assert ctx1["session"]["tool_sequence"] == ["dusun"]

    def test_second_call_sees_two_tools(self) -> None:
        self._call_dusun()
        ctx2 = self._call_validate()
        assert ctx2["session"]["session_call_count"] == 2
        assert ctx2["session"]["tool_sequence"] == ["dusun", "validate_stage"]

    def test_third_call_sees_three_tools(self) -> None:
        self._call_dusun()
        self._call_validate()
        ctx3 = self._call_kavaid()
        assert ctx3["session"]["session_call_count"] == 3
        assert "dusun" in ctx3["session"]["tool_sequence"]
        assert "validate_stage" in ctx3["session"]["tool_sequence"]
        assert "check_kavaid" in ctx3["session"]["tool_sequence"]

    def test_grade_ceiling_is_minimum(self) -> None:
        """İlmelyakîn then Tasdik → ceiling = Tasdik."""
        self._call_dusun()   # İlmelyakîn
        ctx2 = self._call_validate()  # Tasdik (lower)
        assert ctx2["grade_ceiling"] == "Tasdik"

    def test_anomaly_propagates_to_third_call(self) -> None:
        """Anomaly in validate_stage should be visible in check_kavaid's cascade forward."""
        self._call_dusun()
        self._call_validate()  # has 1 anomaly
        ctx3 = self._call_kavaid()
        cf = ctx3["cascade_forward"]
        assert "KV₆ seed low" in cf.get("prior_anomalies", [])

    def test_kavaid_failures_forwarded(self) -> None:
        """KV₆ and KV₇ failures recorded after check_kavaid should appear."""
        self._call_dusun()
        self._call_validate()
        ctx3 = self._call_kavaid()
        cf = ctx3["cascade_forward"]
        assert "KV₆" in cf.get("prior_kavaid_failures", [])
        assert "KV₇" in cf.get("prior_kavaid_failures", [])


# ════════════════════════════════════════════════════════════════════
# 3. Cascade Degradation Flow
# ════════════════════════════════════════════════════════════════════

class TestCascadeDegradation:
    """Test cascade status progression: NOMINAL → DEGRADED → CRITICAL."""

    def _record(self, tool: str, *, anomalies: list[str] | None = None,
                gate: str = "PASS", grade: str = "Tasdik") -> dict:
        ledger = get_ledger()
        ledger.record_call(
            tool,
            grade=grade,
            gate=gate,
            composite_score=0.70,
            anomalies=anomalies or [],
        )
        return build_framework_context(
            tool,
            grade=grade,
            gate=gate,
            composite_score=0.70,
            anomalies=anomalies or [],
            session_context=ledger.get_cumulative_context(),
            cascade_status=ledger.get_cascade_status(),
            cascade_forward=ledger.get_cascade_forward(),
        )

    def test_starts_nominal(self) -> None:
        ctx = self._record("dusun")
        assert ctx["cascade"]["degradation_level"] == "NOMINAL"

    def test_two_anomalies_still_nominal(self) -> None:
        self._record("validate_stage", anomalies=["a1", "a2"])
        ctx = self._record("dusun")
        assert ctx["cascade"]["degradation_level"] == "NOMINAL"

    def test_three_anomalies_triggers_degraded(self) -> None:
        self._record("validate_stage", anomalies=["a1", "a2", "a3"])
        ctx = self._record("dusun")
        assert ctx["cascade"]["degradation_level"] == "DEGRADED"

    def test_fail_gate_triggers_critical(self) -> None:
        self._record("validate_stage", gate="FAIL")
        ctx = self._record("dusun")
        assert ctx["cascade"]["degradation_level"] == "CRITICAL"

    def test_degraded_cascade_forward_has_must_directives(self) -> None:
        self._record("validate_stage", anomalies=["x1", "x2", "x3"])
        ctx = self._record("dusun")
        cf = ctx["cascade_forward"]
        directives = cf.get("forward_directives", [])
        has_must = any("MUST" in d for d in directives)
        assert has_must, f"Expected MUST in directives: {directives}"

    def test_critical_cascade_forward_has_must_directives(self) -> None:
        self._record("validate_stage", gate="FAIL")
        ctx = self._record("dusun")
        cf = ctx["cascade_forward"]
        directives = cf.get("forward_directives", [])
        has_must = any("MUST" in d for d in directives)
        assert has_must, f"Expected MUST in directives: {directives}"

    def test_nominal_cascade_forward_grade_only(self) -> None:
        """Even nominal status may include grade ceiling directive."""
        ctx = self._record("dusun")
        cf = ctx["cascade_forward"]
        directives = cf.get("forward_directives", [])
        # Only grade ceiling directive should be present at NOMINAL
        for d in directives:
            assert "CRITICAL" not in d
            assert "DEGRADED" not in d


# ════════════════════════════════════════════════════════════════════
# 4. Grade Ceiling Enforcement Across Tools
# ════════════════════════════════════════════════════════════════════

class TestGradeCeilingIntegration:
    """Grade ceiling must be the minimum across ALL session calls."""

    def _call(self, tool: str, grade: str) -> dict:
        ledger = get_ledger()
        ledger.record_call(tool, grade=grade, composite_score=0.70)
        return build_framework_context(
            tool,
            grade=grade,
            composite_score=0.70,
            session_context=ledger.get_cumulative_context(),
            cascade_status=ledger.get_cascade_status(),
            cascade_forward=ledger.get_cascade_forward(),
        )

    def test_single_call_ceiling_equals_grade(self) -> None:
        ctx = self._call("dusun", "İlmelyakîn")
        assert ctx["grade_ceiling"] == "İlmelyakîn"

    def test_descending_grades(self) -> None:
        self._call("dusun", "İlmelyakîn")
        ctx = self._call("validate_stage", "Tasdik")
        assert ctx["grade_ceiling"] == "Tasdik"

    def test_ascending_does_not_raise_ceiling(self) -> None:
        """Once Tasavvur is recorded, İlmelyakîn doesn't raise the ceiling.
        
        Note: grade_ceiling in the top-level context reflects the CURRENT
        tool's grade.  The session-level ceiling (min across all calls)
        is in session_context and cascade_forward."""
        self._call("dusun", "Tasavvur")
        ctx = self._call("validate_stage", "İlmelyakîn")
        # The session-level ceiling should be Tasavvur (the minimum)
        assert ctx["session"]["grade_ceiling"] == "Tasavvur"

    def test_ceiling_in_cascade_forward_directives(self) -> None:
        """Cascade forward should reference the session grade ceiling."""
        self._call("dusun", "Tasdik")
        ctx = self._call("validate_stage", "İlmelyakîn")
        cf = ctx["cascade_forward"]
        directives = cf.get("forward_directives", [])
        ceiling_mentioned = any("Tasdik" in d for d in directives)
        assert ceiling_mentioned, f"Expected Tasdik ceiling in directives: {directives}"


# ════════════════════════════════════════════════════════════════════
# 5. Gate History Propagation
# ════════════════════════════════════════════════════════════════════

class TestGateHistoryIntegration:
    """Gate results accumulate and propagate through cascade forward."""

    def _call_with_gate(self, tool: str, gate: str) -> dict:
        ledger = get_ledger()
        ledger.record_call(tool, grade="Tasdik", gate=gate, composite_score=0.70)
        return build_framework_context(
            tool,
            grade="Tasdik",
            gate=gate,
            composite_score=0.70,
            session_context=ledger.get_cumulative_context(),
            cascade_status=ledger.get_cascade_status(),
            cascade_forward=ledger.get_cascade_forward(),
        )

    def test_single_pass_gate(self) -> None:
        ctx = self._call_with_gate("validate_stage", "PASS")
        assert ctx["session"]["gate_history"] == ["PASS"]

    def test_multiple_gates_accumulated(self) -> None:
        self._call_with_gate("validate_stage", "PASS")
        ctx = self._call_with_gate("validate_stage", "WARN")
        assert ctx["session"]["gate_history"] == ["PASS", "WARN"]

    def test_fail_gate_in_cascade_forward(self) -> None:
        self._call_with_gate("validate_stage", "FAIL")
        ctx = self._call_with_gate("dusun", "PASS")
        cf = ctx["cascade_forward"]
        assert "FAIL" in cf.get("prior_gate_summary", [])


# ════════════════════════════════════════════════════════════════════
# 6. Full /dusun Pipeline Simulation (7-Stage)
# ════════════════════════════════════════════════════════════════════

class TestFullPipelineSimulation:
    """Simulate a realistic /dusun pipeline run with 7 validate_stage calls,
    verifying that cascade state evolves correctly."""

    def _validate(self, stage: int, *, gate: str = "PASS",
                  anomalies: list[str] | None = None) -> dict:
        grade = "Tasdik" if gate != "FAIL" else "Tasavvur"
        ledger = get_ledger()
        ledger.record_call(
            "validate_stage",
            grade=grade,
            gate=gate,
            composite_score=0.70,
            kavaid_pass_count=8,
            anomalies=anomalies or [],
            extra={"stage": str(stage)},
        )
        return build_framework_context(
            "validate_stage",
            grade=grade,
            gate=gate,
            composite_score=0.70,
            kavaid_pass_count=8,
            anomalies=anomalies or [],
            session_context=ledger.get_cumulative_context(),
            cascade_status=ledger.get_cascade_status(),
            cascade_forward=ledger.get_cascade_forward(),
        )

    def test_clean_pipeline_stays_nominal(self) -> None:
        for s in range(1, 8):
            ctx = self._validate(s)
        assert ctx["cascade"]["degradation_level"] == "NOMINAL"
        assert ctx["session"]["session_call_count"] == 7

    def test_anomaly_accumulation_across_stages(self) -> None:
        """Stage 2 has 1 anomaly, stage 4 has 2 → total 3 → DEGRADED."""
        self._validate(1)
        self._validate(2, anomalies=["a1"])
        self._validate(3)
        self._validate(4, anomalies=["a2", "a3"])
        ctx = self._validate(5)
        assert ctx["cascade"]["degradation_level"] == "DEGRADED"

    def test_fail_gate_mid_pipeline(self) -> None:
        """FAIL at stage 3 → CRITICAL for all subsequent."""
        self._validate(1)
        self._validate(2)
        self._validate(3, gate="FAIL")
        ctx = self._validate(4)
        assert ctx["cascade"]["degradation_level"] == "CRITICAL"

    def test_grade_ceiling_across_pipeline(self) -> None:
        """All Tasdik except stage 3 FAIL → Tasavvur → ceiling = Tasavvur."""
        self._validate(1)
        self._validate(2)
        self._validate(3, gate="FAIL")  # grade = Tasavvur
        ctx = self._validate(4)
        assert ctx["session"]["grade_ceiling"] == "Tasavvur"

    def test_all_anomalies_forwarded_in_last_stage(self) -> None:
        """Anomalies from multiple stages all appear in the final cascade forward."""
        self._validate(1, anomalies=["early_issue"])
        self._validate(2)
        self._validate(3, anomalies=["mid_issue"])
        ctx = self._validate(4)
        cf = ctx["cascade_forward"]
        assert "early_issue" in cf["prior_anomalies"]
        assert "mid_issue" in cf["prior_anomalies"]

    def test_seven_stage_call_sequence(self) -> None:
        for s in range(1, 8):
            ctx = self._validate(s)
        assert ctx["session"]["tool_sequence"] == ["validate_stage"] * 7


# ════════════════════════════════════════════════════════════════════
# 7. KV₃ Resilience — Integration Level
# ════════════════════════════════════════════════════════════════════

class TestKV3IntegrationResilience:
    """Verify the KV₃ guarantee holds at integration level: no combination
    of ledger + context operations should ever raise an exception that
    would affect the tool return."""

    def test_context_with_none_session(self) -> None:
        """Passing None session_context should not crash."""
        ctx = build_framework_context(
            "dusun",
            grade="Tasdik",
            composite_score=0.70,
            session_context=None,
            cascade_status=None,
        )
        assert "directives" in ctx

    def test_context_with_empty_cascade_forward(self) -> None:
        ctx = build_framework_context(
            "validate_stage",
            grade="Tasdik",
            composite_score=0.70,
            cascade_forward={},
        )
        assert "cascade_forward" in ctx

    def test_context_with_malformed_cascade_forward(self) -> None:
        """Cascade forward with wrong types should not crash."""
        ctx = build_framework_context(
            "validate_stage",
            grade="Tasdik",
            composite_score=0.70,
            cascade_forward={"forward_directives": "not-a-list"},
        )
        # Should still produce a valid envelope
        assert "directives" in ctx

    def test_full_flow_after_reset(self) -> None:
        """After reset, ledger still works for context building."""
        ledger = get_ledger()
        ledger.record_call("dusun", grade="Tasdik")
        reset_ledger()
        ledger2 = get_ledger()
        ledger2.record_call("validate_stage", grade="Tasdik")
        ctx = build_framework_context(
            "validate_stage",
            grade="Tasdik",
            composite_score=0.70,
            session_context=ledger2.get_cumulative_context(),
            cascade_status=ledger2.get_cascade_status(),
            cascade_forward=ledger2.get_cascade_forward(),
        )
        # Should see only 1 call (post-reset)
        assert ctx["session"]["session_call_count"] == 1


# ════════════════════════════════════════════════════════════════════
# 8. Boot Seed → dusun() Integration
# ════════════════════════════════════════════════════════════════════

class TestBootSeedDusunIntegration:
    """Verify that calling dusun() returns results containing the boot
    seed with Hayat Bridge references."""

    def test_dusun_returns_boot_seed_with_hayat(self) -> None:
        """The boot_seed in dusun result should contain Hayat Bridge."""
        from fw_server.dusun import dusun as _dusun
        result = _dusun(text="test structural analysis of a system")
        boot = result.get("boot_seed", "")
        assert "hayat bridge" in boot.lower()

    def test_dusun_boot_seed_has_four_channels(self) -> None:
        from fw_server.dusun import dusun as _dusun
        result = _dusun(text="analyze the integration patterns")
        boot = result.get("boot_seed", "")
        for ch in ["channel 1", "channel 2", "channel 3", "channel 4"]:
            assert ch in boot.lower(), f"Missing {ch} in boot_seed"
