import json
from pathlib import Path

import pytest

from recoil.tools import boardability_doctor as bd


SCRIPT_LINES = [
    "FADE IN:",
    "",
    "INT. POD BAY - NIGHT",
    "",
    "JADE (V.O.)",
    "I count the silence before the lock gives way.",
    "",
    "Jade wedges into the pod, shoulders wider than the pod should allow.",
    "",
    "She does something with the panel.",
    "",
    "The hatch exhales open.",
]

BIBLE_TEXT = (
    "Signature Tell: holds her breath when a threshold changes. "
    "VO opens are canonical."
)


def _valid_findings():
    return [
        {
            "id": "BD001",
            "category": "unrenderable_authorial",
            "severity": "P1",
            "line": 8,
            "quoted_text": "shoulders wider than the pod should allow",
            "why": "The line asks for an impossible comparison instead of renderable contact.",
            "suggested_fix": "Show her shoulders pressing into both pod walls.",
            "suggested_mechanism": "patch_script",
        },
        {
            "id": "BD002",
            "category": "under_specified_staging",
            "severity": "P2",
            "line": 10,
            "quoted_text": "She does something with the panel.",
            "why": "The action is too vague to board without inventing business.",
            "suggested_fix": "Name the control, hand, and visible result.",
            "suggested_mechanism": "patch_blocking",
        },
    ]


@pytest.fixture
def fixture_project(tmp_path, monkeypatch):
    monkeypatch.setenv("RECOIL_PROJECTS_ROOT", str(tmp_path))
    monkeypatch.setenv("RECOIL_CLAUDE_TRANSPORT", "cli")
    (tmp_path / ".recoil-data-root").touch()

    project_root = tmp_path / "tartarus"
    episodes_dir = project_root / "scripting" / "episodes"
    bible_dir = project_root / "scripting" / "bible"
    episodes_dir.mkdir(parents=True)
    bible_dir.mkdir(parents=True)
    script_path = episodes_dir / "ep_001.md"
    script_path.write_text("\n".join(SCRIPT_LINES) + "\n", encoding="utf-8")
    bible_path = bible_dir / "series_bible.md"
    bible_path.write_text(BIBLE_TEXT + "\n", encoding="utf-8")

    calls = []

    def fake_claude_cli_call(user_prompt, *, system_prompt=None, model=None):
        calls.append(
            {
                "user_prompt": user_prompt,
                "system_prompt": system_prompt,
                "model": model,
            }
        )
        return json.dumps({"findings": _valid_findings(), "summary": "Two issues."})

    monkeypatch.setattr(bd, "claude_cli_call", fake_claude_cli_call)
    return {
        "root": tmp_path,
        "project_root": project_root,
        "script_path": script_path,
        "bible_path": bible_path,
        "calls": calls,
    }


def test_uses_claude_cli_not_gemini(fixture_project):
    bd.run_boardability("tartarus", 1)

    assert fixture_project["calls"]
    source = Path(bd.__file__).read_text(encoding="utf-8")
    assert "genai" not in source
    assert "google.generativeai" not in source
    assert "from recoil.core.claude_cli import" in source


def test_canon_awareness_in_prompt(fixture_project):
    bd.run_boardability("tartarus", 1)

    system_prompt = fixture_project["calls"][0]["system_prompt"]
    assert "holds her breath" in system_prompt
    assert "DO NOT flag" in system_prompt
    assert "V.O." in system_prompt


def test_canon_vo_not_flagged(fixture_project):
    brief = bd.run_boardability("tartarus", 1)

    assert all(
        "V.O." not in finding["quoted_text"] and "JADE" not in finding["quoted_text"]
        for finding in brief["findings"]
    )


def test_flags_unrenderable_authorial(fixture_project):
    brief = bd.run_boardability("tartarus", 1)

    assert any(
        finding["severity"] == "P1"
        and finding["category"] == "unrenderable_authorial"
        and "shoulders wider than the pod should allow" in finding["quoted_text"]
        for finding in brief["findings"]
    )


@pytest.mark.parametrize(
    "raw_response",
    [
        "not json",
        json.dumps({"findings": "not a list", "summary": "bad shape"}),
        json.dumps({"findings": []}),
    ],
)
def test_top_level_validation_fail_loud(fixture_project, monkeypatch, raw_response):
    monkeypatch.setattr(bd, "claude_cli_call", lambda *args, **kwargs: raw_response)

    with pytest.raises(ValueError):
        bd.run_boardability("tartarus", 1)


@pytest.mark.parametrize(
    "mutate",
    [
        lambda finding: {**finding, "severity": "P9"},
        lambda finding: {**finding, "category": "not_a_category"},
        lambda finding: {key: value for key, value in finding.items() if key != "why"},
    ],
)
def test_finding_schema_validation_fail_loud(fixture_project, monkeypatch, mutate):
    findings = _valid_findings()
    findings[0] = mutate(findings[0])
    monkeypatch.setattr(
        bd,
        "claude_cli_call",
        lambda *args, **kwargs: json.dumps(
            {"findings": findings, "summary": "Schema failure."}
        ),
    )

    with pytest.raises(ValueError):
        bd.run_boardability("tartarus", 1)

    assert not (fixture_project["project_root"] / "_pipeline" / "state").exists()


def test_transport_hardfail(fixture_project, monkeypatch):
    monkeypatch.setattr(bd, "claude_transport", lambda: "sdk")

    with pytest.raises(RuntimeError):
        bd.run_boardability("tartarus", 1)


def test_gate_exit_code(fixture_project, monkeypatch):
    assert bd.main(["--project", "tartarus", "--episode", "1", "--gate"]) == 2

    p2_only = _valid_findings()
    p2_only[0] = {**p2_only[0], "severity": "P2"}
    monkeypatch.setattr(
        bd,
        "claude_cli_call",
        lambda *args, **kwargs: json.dumps(
            {"findings": p2_only, "summary": "No blocking issues."}
        ),
    )
    assert bd.main(["--project", "tartarus", "--episode", "1", "--gate"]) == 0


def test_resolve_inputs_uses_ssot_path(fixture_project):
    script_path, script_text, bible_paths, bible_text = bd.resolve_inputs("tartarus", 1)

    assert script_path == fixture_project["script_path"]
    assert fixture_project["root"] in script_path.parents
    assert "shoulders wider than the pod should allow" in script_text
    assert bible_paths == [fixture_project["bible_path"]]
    assert "holds her breath" in bible_text
    with pytest.raises(FileNotFoundError):
        bd.resolve_inputs("tartarus", 2)


def test_write_brief_creates_state_dir(fixture_project):
    brief = bd.run_boardability("tartarus", 1)
    state_dir = fixture_project["project_root"] / "_pipeline" / "state"
    assert not state_dir.exists()

    path = bd.write_brief(brief, "tartarus", 1)

    assert state_dir.exists()
    assert path.exists()
    assert path.parent == state_dir


def test_brief_is_note_shaped(fixture_project):
    brief = bd.run_boardability("tartarus", 1)

    assert brief["transport"] == "cli"
    assert all(
        finding["suggested_mechanism"] in bd.MECHANISMS
        for finding in brief["findings"]
    )
