from __future__ import annotations

import logging
import sys
import types
from pathlib import Path

import pytest

from recoil.core import claude_cli
from recoil.tools import engine_constants as ec
from recoil.tools import validate_arc


@pytest.fixture(autouse=True)
def _reset_llm_gate_state(monkeypatch: pytest.MonkeyPatch) -> None:
    ec._warned_no_transport = False
    ec._cached_anthropic_client = None
    ec._anthropic_client_checked = False
    monkeypatch.delenv("ANTHROPIC_API_KEY", raising=False)


def test_llm_gate_cli_lane_routes_through_claude_cli(
    monkeypatch: pytest.MonkeyPatch,
) -> None:
    calls: list[dict] = []

    def fake_cli_call(prompt, images=None, *, system_prompt=None, model=None, timeout_s=900):
        calls.append(
            {
                "prompt": prompt,
                "images": images,
                "system_prompt": system_prompt,
                "model": model,
                "timeout_s": timeout_s,
            }
        )
        return "  BUCKET: TECHNICAL_HACK\n"

    monkeypatch.setattr(claude_cli, "claude_transport", lambda: "cli")
    monkeypatch.setattr(claude_cli, "claude_cli_call", fake_cli_call)

    assert ec.llm_gate_call("classify this", model="claude-test") == "BUCKET: TECHNICAL_HACK"
    assert calls == [
        {
            "prompt": "classify this",
            "images": None,
            "system_prompt": None,
            "model": "claude-test",
            "timeout_s": 900,
        }
    ]


def test_llm_gate_cli_error_warns_once_across_degraded_calls(
    monkeypatch: pytest.MonkeyPatch,
    caplog: pytest.LogCaptureFixture,
) -> None:
    monkeypatch.setattr(claude_cli, "claude_transport", lambda: "cli")

    def boom(*_args, **_kwargs):
        raise claude_cli.ClaudeCliError("cli unavailable")

    monkeypatch.setattr(claude_cli, "claude_cli_call", boom)

    with caplog.at_level(logging.WARNING, logger=ec.__name__):
        assert ec.llm_gate_call("one", model="claude-test") is None
        assert ec.llm_gate_call("two", model="claude-test") is None

    messages = [
        record.getMessage()
        for record in caplog.records
        if "LLM gates degraded: no usable Claude transport" in record.getMessage()
    ]
    assert messages == [
        "LLM gates degraded: no usable Claude transport (lane=cli) — narrative LLM gates will skip"
    ]


def test_sdk_no_key_get_client_and_llm_gate_share_one_warning(
    monkeypatch: pytest.MonkeyPatch,
    caplog: pytest.LogCaptureFixture,
) -> None:
    monkeypatch.setattr(claude_cli, "claude_transport", lambda: "sdk")
    monkeypatch.setitem(sys.modules, "anthropic", types.SimpleNamespace())

    with caplog.at_level(logging.WARNING, logger=ec.__name__):
        assert ec.get_anthropic_client() is None
        assert ec.llm_gate_call("classify this", model="claude-test") is None

    messages = [
        record.getMessage()
        for record in caplog.records
        if "LLM gates degraded: no usable Claude transport" in record.getMessage()
    ]
    assert messages == [
        "LLM gates degraded: no usable Claude transport (lane=sdk) — narrative LLM gates will skip"
    ]


def test_validate_arc_uses_llm_gate_without_local_client_fallback(
    monkeypatch: pytest.MonkeyPatch,
    tmp_path: Path,
) -> None:
    source = Path(validate_arc.__file__).read_text()
    assert "get_anthropic_client = lambda" not in source
    assert "client = get_anthropic_client()" not in source

    calls: list[dict] = []

    def fake_llm_gate_call(prompt: str, *, model: str, max_tokens: int = 200):
        calls.append({"prompt": prompt, "model": model, "max_tokens": max_tokens})
        return "BUCKET: TECHNICAL_HACK"

    monkeypatch.setattr(validate_arc, "llm_gate_call", fake_llm_gate_call)

    project = tmp_path / "project"
    project.mkdir()
    (project / "structure_outline.md").write_text(
        "# SEQUENCE 1\nJade solves the airlock timing puzzle with a patch cable.\n"
    )

    assert validate_arc.track_semantic_escalation(project) == {"1": "technical_hack"}
    assert calls and calls[0]["model"] == validate_arc.ANTHROPIC_HAIKU
    assert calls[0]["max_tokens"] == 50
