from __future__ import annotations

import json
import re
from pathlib import Path

import pytest
from fastapi import HTTPException
from starlette.testclient import TestClient

from recoil.pipeline.core.persistence import save_scene
from recoil.pipeline.core.take import Beat, Scene
from recoil.pipeline.orchestrator.batch_selector import resolve
from recoil.workspace.server import _validate_project, app


def _tartarus_ep001_available() -> bool:
    try:
        resolve("EP001_CONT_001", "tartarus")
        return True
    except Exception:
        return False


def _make_project(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> tuple[str, Path, Path, Path]:
    projects_root = tmp_path / "projects"
    projects_root.mkdir()
    (projects_root / ".recoil-data-root").write_text("recoil-data-root\n")
    project = "fixture"
    project_root = projects_root / project
    project_root.mkdir()
    (project_root / "project_config.json").write_text("{}")
    monkeypatch.setenv("RECOIL_PROJECTS_ROOT", str(projects_root))

    scenes_dir = project_root / "_pipeline" / "state" / "orchestration" / "scenes"
    storyboards_dir = project_root / "prep" / "ep_001" / "storyboards"
    scenes_dir.mkdir(parents=True)
    storyboards_dir.mkdir(parents=True)
    return project, project_root, scenes_dir, storyboards_dir


def _board(artifact: str) -> dict:
    return {
        "status": "proposed",
        "artifact": artifact,
        "source_sha256": "deadbeef",
        "approved_by": None,
        "updated_at": "2026-06-20T19:52:19Z",
        "story_gate": {
            "mode": "shadow",
            "route": "ok",
            "confidence": 0.92,
            "verdict_path": "prep/ep_001/storyboards/EP001_CONT_001_v04.verdict.json",
            "LEAK_KEY": "must-not-cross-wire",
        },
    }


def _write_board_scene(path: Path, board: dict) -> None:
    save_scene(
        Scene(
            scene_id="BATCH_001",
            beats=[Beat(beat_id="EP001_CONT_001", board=board)],
        ),
        path,
    )


@pytest.fixture
def board_selector_project(tmp_path, monkeypatch):
    project, project_root, scenes_dir, storyboards_dir = _make_project(tmp_path, monkeypatch)
    pointer = "prep/ep_001/storyboards/EP001_CONT_001_v04.png"
    _write_board_scene(scenes_dir / "ep_001_BATCH_001.json", _board(pointer))
    (storyboards_dir / "EP001_CONT_001_v04.png.json").write_text(
        json.dumps(
            {
                "status": "candidate",
                "approved_by": None,
                "LEAK_KEY": "must-not-cross-wire",
            }
        ),
        encoding="utf-8",
    )
    (storyboards_dir / "EP001_CONT_001_v05.png.json").write_text(
        json.dumps({"status": "candidate", "approved_by": None}),
        encoding="utf-8",
    )
    (project_root / pointer).write_bytes(b"png")
    (storyboards_dir / "EP001_CONT_001_v05.png").write_bytes(b"png")
    (scenes_dir / "ep_002_BATCH_001.json").write_text("{not json", encoding="utf-8")
    return {
        "project": project,
        "project_root": project_root,
        "scenes_dir": scenes_dir,
        "storyboards_dir": storyboards_dir,
    }


def test_board_selector_route_serves_pointer_faithful_projection(board_selector_project):
    assert "/api/board/{selector}" in [r.path for r in app.routes if hasattr(r, "path")]
    client = TestClient(app)

    r = client.get("/api/board/EP001_CONT_001?project=fixture")

    assert r.status_code == 200
    body = r.json()
    assert body["board_artifact"] == "prep/ep_001/storyboards/EP001_CONT_001_v04.png"
    assert body["newer_unpointed_versions"] == 1
    assert body["board_artifact"] != "prep/ep_001/storyboards/EP001_CONT_001_v05.png"
    assert body["provenance"]["status"] == "proposed"
    assert body["provenance"]["version"] == 4
    assert set(body["provenance"]["gate"]) == {
        "mode",
        "route",
        "confidence",
        "verdict_path",
    }
    assert set(body["board"]) == {"status", "approved_by"}
    assert "LEAK_KEY" not in (body["board"] or {})
    assert "LEAK_KEY" not in body["provenance"]["gate"]
    assert body["schemaVersion"] == 1
    assert "schema_version" not in body


def test_board_selector_route_status_code_contract(board_selector_project):
    client = TestClient(app)

    malformed = client.get("/api/board/garbage?project=fixture")
    assert malformed.status_code == 400
    assert malformed.json()["selector"] == "garbage"

    absent = client.get("/api/board/EP999_CONT_001?project=fixture")
    assert absent.status_code == 404
    assert absent.json()["selector"] == "EP999_CONT_001"


def test_board_selector_route_scene_corruption_is_500(board_selector_project):
    client = TestClient(app, raise_server_exceptions=False)

    r = client.get("/api/board/EP002_CONT_001?project=fixture")

    assert r.status_code == 500


@pytest.mark.parametrize("project", ["../etc", "a/b", "a%00"])
def test_board_selector_route_rejects_project_boundary_values(
    board_selector_project, project
):
    client = TestClient(app)

    r = client.get(f"/api/board/EP001_CONT_001?project={project}")

    assert r.status_code == 400


@pytest.mark.skipif(
    not _tartarus_ep001_available(),
    reason="live tartarus EP001 unavailable in this environment (hermetic CI)",
)
def test_board_selector_route_live_tartarus_smoke_is_pointer_faithful():
    client = TestClient(app)

    r = client.get("/api/board/EP001_CONT_001?project=tartarus")

    assert r.status_code == 200
    body = r.json()
    assert body["board_artifact"] is not None
    assert body["schemaVersion"] == 1
    version = int(re.search(r"_v(\d+)(?=\.png$)", body["board_artifact"]).group(1))
    assert body["provenance"]["version"] == version


@pytest.mark.parametrize("project", ["../etc", "a/b"])
def test_validate_project_still_resolves_from_server_and_returns_400(project):
    with pytest.raises(HTTPException) as excinfo:
        _validate_project(project)

    assert excinfo.value.status_code == 400
