"""Tests for Phase 3 — Channel 1: Tool Description Enrichment.

Verifies that every core tool's docstring contains the enforcement
language required by the Hayat Bridge architecture (AX27+T10).
The MCP tool description is the FIRST thing the LLM reads before
calling a tool — it is the only pre-invocation enforcement surface.
"""

import inspect
import re

import pytest

# Import the server module to get the actual tool functions
from fw_server import server


# ---------------------------------------------------------------------------
# Helpers
# ---------------------------------------------------------------------------

_CORE_TOOLS = [
    "run_bileshke_pipeline",
    "check_kavaid",
    "validate_stage",
    "dusun_tool",
]

_ENFORCEMENT_KEYWORDS = {"MUST", "MUST NOT", "NEVER"}


def _get_docstring(tool_name: str) -> str:
    """Return the docstring of a core tool function."""
    fn = getattr(server, tool_name, None)
    assert fn is not None, f"Function {tool_name} not found in server module"
    doc = inspect.getdoc(fn)
    assert doc, f"Function {tool_name} has no docstring"
    return doc


# ---------------------------------------------------------------------------
# TestEnforcementPresence — every docstring has an Enforcement block
# ---------------------------------------------------------------------------


class TestEnforcementPresence:
    """Every core tool docstring MUST contain an 'Enforcement' section."""

    @pytest.mark.parametrize("tool_name", _CORE_TOOLS)
    def test_enforcement_section_exists(self, tool_name):
        doc = _get_docstring(tool_name)
        assert "Enforcement" in doc, (
            f"{tool_name} docstring missing 'Enforcement' section"
        )

    @pytest.mark.parametrize("tool_name", _CORE_TOOLS)
    def test_enforcement_marked_mandatory(self, tool_name):
        doc = _get_docstring(tool_name)
        assert "MANDATORY" in doc, (
            f"{tool_name} Enforcement section not marked MANDATORY"
        )


# ---------------------------------------------------------------------------
# TestEnforcementKeywords — every docstring uses MUST/MUST NOT/NEVER
# ---------------------------------------------------------------------------


class TestEnforcementKeywords:
    """Enforcement language MUST use binding keywords (MUST, MUST NOT, NEVER)."""

    @pytest.mark.parametrize("tool_name", _CORE_TOOLS)
    def test_contains_must(self, tool_name):
        doc = _get_docstring(tool_name)
        assert "MUST" in doc, f"{tool_name} missing 'MUST' keyword"

    @pytest.mark.parametrize("tool_name", _CORE_TOOLS)
    def test_contains_must_not(self, tool_name):
        doc = _get_docstring(tool_name)
        assert "MUST NOT" in doc, f"{tool_name} missing 'MUST NOT' keyword"

    @pytest.mark.parametrize("tool_name", _CORE_TOOLS)
    def test_contains_never(self, tool_name):
        doc = _get_docstring(tool_name)
        assert "NEVER" in doc, f"{tool_name} missing 'NEVER' keyword"

    @pytest.mark.parametrize("tool_name", _CORE_TOOLS)
    def test_minimum_enforcement_keyword_count(self, tool_name):
        """Each docstring should have at least 3 enforcement keywords."""
        doc = _get_docstring(tool_name)
        count = sum(1 for kw in _ENFORCEMENT_KEYWORDS if kw in doc)
        assert count >= 3, (
            f"{tool_name} has only {count}/3 enforcement keyword types"
        )


# ---------------------------------------------------------------------------
# TestFrameworkContextMention — _framework_context referenced
# ---------------------------------------------------------------------------


class TestFrameworkContextMention:
    """Every core tool MUST mention _framework_context in its docstring."""

    @pytest.mark.parametrize("tool_name", _CORE_TOOLS)
    def test_framework_context_in_returns(self, tool_name):
        doc = _get_docstring(tool_name)
        assert "_framework_context" in doc, (
            f"{tool_name} docstring does not mention _framework_context"
        )

    @pytest.mark.parametrize("tool_name", _CORE_TOOLS)
    def test_directives_field_referenced(self, tool_name):
        """The directives list inside _framework_context should be mentioned."""
        doc = _get_docstring(tool_name)
        assert "_framework_context.directives" in doc, (
            f"{tool_name} does not reference _framework_context.directives"
        )


# ---------------------------------------------------------------------------
# TestGradeConstraint — grade ceiling language
# ---------------------------------------------------------------------------


class TestGradeConstraint:
    """Core tools producing grades MUST warn against grade inflation."""

    _GRADE_TOOLS = [
        "run_bileshke_pipeline",
        "validate_stage",
        "dusun_tool",
    ]

    @pytest.mark.parametrize("tool_name", _GRADE_TOOLS)
    def test_grade_ceiling_language(self, tool_name):
        doc = _get_docstring(tool_name)
        # Must mention "CEILING" or "ceiling" in context of grade
        doc_upper = doc.upper()
        assert "CEILING" in doc_upper, (
            f"{tool_name} missing grade ceiling language"
        )

    @pytest.mark.parametrize("tool_name", _GRADE_TOOLS)
    def test_grade_inflation_prohibition(self, tool_name):
        """Must explicitly prohibit grade inflation."""
        doc = _get_docstring(tool_name)
        # Check for "MUST NOT claim" with "higher grade" or "grade inflation"
        has_prohibition = (
            "MUST NOT claim a higher grade" in doc
            or "grade inflation" in doc.lower()
        )
        assert has_prohibition, (
            f"{tool_name} missing explicit grade inflation prohibition"
        )


# ---------------------------------------------------------------------------
# TestKV4Warning — convergence bound language
# ---------------------------------------------------------------------------


class TestKV4Warning:
    """Composite-producing tools MUST warn about KV₄ convergence bound."""

    _COMPOSITE_TOOLS = [
        "run_bileshke_pipeline",
        "dusun_tool",
    ]

    @pytest.mark.parametrize("tool_name", _COMPOSITE_TOOLS)
    def test_kv4_error_threshold(self, tool_name):
        doc = _get_docstring(tool_name)
        assert "0.95" in doc, (
            f"{tool_name} missing 0.95 KV₄ error threshold"
        )

    @pytest.mark.parametrize("tool_name", _COMPOSITE_TOOLS)
    def test_kv4_never_success(self, tool_name):
        """High convergence MUST be flagged as error, not success."""
        doc = _get_docstring(tool_name)
        assert "NEVER" in doc, (
            f"{tool_name} missing NEVER keyword for KV₄ warning"
        )


# ---------------------------------------------------------------------------
# TestGateEnforcement — validate_stage gate language
# ---------------------------------------------------------------------------


class TestGateEnforcement:
    """validate_stage MUST declare gate verdicts as BINDING."""

    def test_binding_keyword(self):
        doc = _get_docstring("validate_stage")
        assert "BINDING" in doc, "validate_stage missing BINDING keyword"

    def test_pass_warn_fail_described(self):
        doc = _get_docstring("validate_stage")
        for verdict in ("PASS", "WARN", "FAIL"):
            assert verdict in doc, (
                f"validate_stage missing {verdict} verdict description"
            )

    def test_fail_prohibition(self):
        """FAIL gate MUST prohibit marking stage as complete."""
        doc = _get_docstring("validate_stage")
        assert "MUST NOT" in doc and "FAIL" in doc, (
            "validate_stage missing FAIL override prohibition"
        )

    def test_anomaly_disclosure_required(self):
        doc = _get_docstring("validate_stage")
        assert "Anomalies MUST be disclosed" in doc or (
            "anomalies" in doc.lower() and "MUST" in doc
        ), "validate_stage missing anomaly disclosure requirement"


# ---------------------------------------------------------------------------
# TestKavaidEnforcement — check_kavaid enforcement
# ---------------------------------------------------------------------------


class TestKavaidEnforcement:
    """check_kavaid MUST enforce kavaid disclosure."""

    def test_all_pass_flag_mentioned(self):
        doc = _get_docstring("check_kavaid")
        assert "all_pass" in doc, "check_kavaid missing all_pass flag reference"

    def test_failure_must_be_reported(self):
        doc = _get_docstring("check_kavaid")
        assert "MUST report" in doc, (
            "check_kavaid missing MUST report directive"
        )

    def test_kv7_independence_warning(self):
        """KV₇ failure has special significance — must be mentioned."""
        doc = _get_docstring("check_kavaid")
        has_kv7 = "KV₇" in doc or "KV7" in doc or "independence" in doc.lower()
        assert has_kv7, "check_kavaid missing KV₇/independence warning"


# ---------------------------------------------------------------------------
# TestDusunBootLanguage — dusun_tool as T1 Boot neuron
# ---------------------------------------------------------------------------


class TestDusunBootLanguage:
    """dusun_tool MUST identify itself as T1 Boot and primary entry point."""

    def test_t1_boot_mentioned(self):
        doc = _get_docstring("dusun_tool")
        assert "T1" in doc and "Boot" in doc, (
            "dusun_tool missing T1 Boot identification"
        )

    def test_primary_entry_point(self):
        doc = _get_docstring("dusun_tool")
        has_primary = "PRIMARY" in doc or "primary" in doc
        assert has_primary, "dusun_tool missing primary entry point language"

    def test_unavailability_fallback(self):
        """Must warn about degradation when dusun is unavailable."""
        doc = _get_docstring("dusun_tool")
        assert "Tasavvur" in doc, (
            "dusun_tool missing Tasavvur fallback for unavailability"
        )

    def test_ungrounded_marker(self):
        """Must mention [UNGROUNDED] marker for when tool is offline."""
        doc = _get_docstring("dusun_tool")
        assert "UNGROUNDED" in doc, (
            "dusun_tool missing [UNGROUNDED] marker guidance"
        )

    def test_patterns_applied_enforcement(self):
        """Must enforce use of patterns_applied list."""
        doc = _get_docstring("dusun_tool")
        assert "patterns_applied" in doc, (
            "dusun_tool missing patterns_applied enforcement"
        )


# ---------------------------------------------------------------------------
# TestCoverageCompleteness — T17 structural maximum language
# ---------------------------------------------------------------------------


class TestCoverageCompleteness:
    """Tools MUST reference T17 structural maximum (6/7)."""

    def test_bileshke_mentions_coverage_bound(self):
        doc = _get_docstring("run_bileshke_pipeline")
        assert "6/7" in doc, (
            "run_bileshke_pipeline missing 6/7 coverage bound"
        )

    def test_bileshke_mentions_ahfa(self):
        doc = _get_docstring("run_bileshke_pipeline")
        assert "Ahfâ" in doc or "Ahfa" in doc, (
            "run_bileshke_pipeline missing Ahfâ reference"
        )


# ---------------------------------------------------------------------------
# TestDocstringLength — enriched docstrings are substantively longer
# ---------------------------------------------------------------------------


class TestDocstringLength:
    """Enriched docstrings should be substantively longer than bare minimum."""

    @pytest.mark.parametrize("tool_name", _CORE_TOOLS)
    def test_minimum_length(self, tool_name):
        """Each enriched docstring should be at least 400 chars."""
        doc = _get_docstring(tool_name)
        assert len(doc) >= 400, (
            f"{tool_name} docstring too short ({len(doc)} chars) — "
            "enforcement language likely missing"
        )

    @pytest.mark.parametrize("tool_name", _CORE_TOOLS)
    def test_enforcement_block_is_substantial(self, tool_name):
        """The Enforcement section should have multiple bullet points."""
        doc = _get_docstring(tool_name)
        enforcement_start = doc.find("Enforcement")
        if enforcement_start == -1:
            pytest.fail(f"{tool_name} missing Enforcement section")
        enforcement_text = doc[enforcement_start:]
        bullet_count = enforcement_text.count("- ")
        assert bullet_count >= 3, (
            f"{tool_name} Enforcement section has only {bullet_count} "
            "bullet points — need at least 3"
        )
