"""Ticket #000001 §7 Phase 0 — deterministic cross-language guard.

Two contracts under test:

1. **English byte-identity.** `guard()` returns None for any
   pure-ASCII query without inverted punctuation — including the
   #000053/#000054 acronym shapes (`CPU`/`GPU`/`AI`/`ML`/`DNA`).
   A None decision means `query()` does nothing, so the English
   retrieval path is unchanged by construction.
2. **Cross-language behaviour.** The non-English signal fires on
   `¿`/`¡`/accented letters; a query with a surviving content token
   strips es function words from retrieval only; a pure-function-word
   query fails closed to UNGROUNDED *before* retrieval/LLM via the
   Merkle-auditable reject-DAG path (same shape as the
   `quantifier_should_reject` path).

No network. The integration test uses a stub corpus + a chat client
that raises if the LLM is reached (proving the pre-LLM short-circuit).
"""

from __future__ import annotations

from collections.abc import Iterator

import pytest

from arborist.qa.crosslang import (
    CrossLanguageDecision,
    guard,
    strip_for_retrieval,
)


# --- Contract 1: English / acronym byte-identity (the #000053/#000054 guard)

@pytest.mark.parametrize(
    "q",
    [
        "What is anarcho-capitalism?",
        "what is a CPU?",
        "GPU vs CPU performance",
        "AI and ML differences",
        "what is DNA?",
        "Who is Russell Ballestrini?",
        "tell me all there is to know about FBI history",
        "",
    ],
)
def test_english_and_acronyms_are_byte_identical_noop(q):
    # None → query() does nothing → English path unchanged.
    assert guard(q) is None


# --- Contract 2: signal + fail-closed semantics

def test_signal_fires_with_content_token_not_fail_closed():
    d = guard("¿Qué es el anarcocapitalismo?")
    assert isinstance(d, CrossLanguageDecision)
    assert d.fail_closed is False
    assert d.content_tokens == ("anarcocapitalismo",)
    # ASCII regex truncates "¿Qué" → "Qu"; es stoppack catches it.
    assert set(d.dropped) == {"Qu", "es", "el"}


@pytest.mark.parametrize("q", ["¿Qué es el?", "¿qué es?", "¡Es la de los!"])
def test_pure_function_words_fail_closed(q):
    d = guard(q)
    assert d is not None and d.fail_closed is True
    assert d.content_tokens == ()


def test_bare_ascii_function_words_do_not_fire_english_safe():
    # No ¿/¡, no accent → no signal → None. We never guess language
    # from bare ASCII (that would risk English false-positives).
    assert guard("es la de los") is None


def test_accented_letter_alone_triggers_signal():
    # No inverted punctuation, but a non-ASCII letter still signals.
    d = guard("relatividad de Einstein según teoría")
    assert d is not None
    assert "Einstein" in d.content_tokens  # proper noun survives


def test_strip_for_retrieval_drops_es_keeps_content_in_order():
    d = guard("¿Qué es el anarcocapitalismo?")
    assert strip_for_retrieval("¿Qué es el anarcocapitalismo?", d) == "anarcocapitalismo"
    # Mixed: English content tokens are retained (downstream applies
    # its own English stopword filter).
    d2 = guard("¿qué es la teoría anarcocapitalismo theory?")
    out = strip_for_retrieval("¿qué es la teoría anarcocapitalismo theory?", d2)
    assert "anarcocapitalismo" in out and "theory" in out
    assert "es" not in out.split() and "la" not in out.split()


def test_reason_strings_distinguish_the_two_paths():
    fc = guard("¿Qué es el?")
    ok = guard("¿Qué es el anarcocapitalismo?")
    assert "fail closed" in fc.reason.lower()
    assert "stripped" in ok.reason.lower()


# --- Contract 2 (integration): query() short-circuits pre-LLM, auditable

class _NoLLM:
    """Chat client that proves the LLM was never reached."""

    def chat_completion(self, messages, **kwargs) -> str:  # noqa: D401
        raise AssertionError("LLM must not be called on the fail-closed path")


def _ingest_one(tmp_path, text, title):
    from arborist.document import Document
    from arborist.ingest import ingest_source
    from arborist.source import Source
    from arborist.store import connect

    class FakeSource(Source):
        source_type = "test"

        def __init__(self, docs):
            self.docs = docs

        def iter_documents(self) -> Iterator[Document]:
            yield from self.docs

    shard = tmp_path / "shard.db"
    conn = connect(shard)
    try:
        ingest_source(
            conn,
            FakeSource([Document(uri="t://d", content=text, source_type="test", title=title)]),
        )
    finally:
        conn.close()
    return shard


def test_query_fail_closed_is_pre_llm_and_merkle_auditable(tmp_path):
    from arborist.qa.dag import verify_run_dag
    from arborist.qa.query import DEFAULT_QUERY_POLICY, query

    shard = _ingest_one(tmp_path, "Anarcho-capitalism is a political philosophy. " * 40,
                         "Anarcho-capitalism")
    result = query(
        question="¿Qué es el?",            # pure function words → fail closed
        qa_db=tmp_path / "qa.db",
        chat_client=_NoLLM(),               # raises if the LLM is reached
        model_id="stub",
        single_db=shard,
        policy=dict(DEFAULT_QUERY_POLICY, answer_mode="claim_lattice",
                    crosslang_guard_enabled=True),
    )
    assert result["status"] == "cross_language_unsupported"
    assert result["audit_mode"] == "UNGROUNDED"
    assert result["cache_key"] is None
    assert result["lookup_path"] == "preflight"
    assert result["violations"][0]["kind"] == "CROSS_LANGUAGE_UNSUPPORTED"
    # 3-stage reject DAG recomputes (same auditability as broad-reject).
    assert verify_run_dag(result["run_dag_blob"]) is True


def test_query_english_does_not_trip_the_guard(tmp_path):
    from arborist.qa.client import StubClient
    from arborist.qa.query import DEFAULT_QUERY_POLICY, query

    shard = _ingest_one(tmp_path, "Anarcho-capitalism is a political philosophy. " * 40,
                        "Anarcho-capitalism")
    result = query(
        question="What is anarcho-capitalism?",
        qa_db=tmp_path / "qa.db",
        chat_client=StubClient(answer="Anarcho-capitalism is a philosophy. [E1]\n"),
        model_id="stub",
        single_db=shard,
        policy=dict(DEFAULT_QUERY_POLICY, answer_mode="claim_lattice",
                    crosslang_guard_enabled=True),  # guard ON, English still safe
    )
    # Even with the guard enabled, the English path never reaches the
    # cross-language status (guard() returns None for pure ASCII).
    assert result["status"] != "cross_language_unsupported"


def test_query_field_case_proceeds_past_guard(tmp_path):
    """`¿Qué es el anarcocapitalismo?` is NOT fail-closed (a content
    token survives) → it must proceed past the guard (no pre-retrieval
    short-circuit), exercising the stripped-retrieval path."""
    from arborist.qa.client import StubClient
    from arborist.qa.query import DEFAULT_QUERY_POLICY, query

    shard = _ingest_one(tmp_path, "Unrelated content about gardening. " * 40, "Gardening")
    result = query(
        question="¿Qué es el anarcocapitalismo?",
        qa_db=tmp_path / "qa.db",
        chat_client=StubClient(answer="x [E1]\n"),
        model_id="stub",
        single_db=shard,
        policy=dict(DEFAULT_QUERY_POLICY, answer_mode="claim_lattice",
                    crosslang_guard_enabled=True),
    )
    assert result["status"] != "cross_language_unsupported"


def test_flag_off_reverts_to_legacy_behaviour(tmp_path):
    """Default policy (flag OFF) → the guard is fully bypassed: even a
    pure-function-word Spanish query does NOT short-circuit, proving
    the A/B baseline for experimentation is byte-for-byte legacy."""
    from arborist.qa.client import StubClient
    from arborist.qa.query import DEFAULT_QUERY_POLICY, query

    assert DEFAULT_QUERY_POLICY["crosslang_guard_enabled"] is False
    shard = _ingest_one(tmp_path, "Unrelated content about gardening. " * 40, "Gardening")
    result = query(
        question="¿Qué es el?",            # would fail-closed IF the flag were on
        qa_db=tmp_path / "qa.db",
        chat_client=StubClient(answer="x [E1]\n"),
        model_id="stub",
        single_db=shard,
        policy=dict(DEFAULT_QUERY_POLICY, answer_mode="claim_lattice"),  # flag OFF
    )
    assert result["status"] != "cross_language_unsupported"
