"""고증 검토 API — Codex PR #82 P1 둘을 **실제 요청**으로 잠근다: 인자 순서(/verdicts 500) · episode 소속·경로 이탈.
★프로젝트·에피소드는 DB 에 직접 심는다 — API 로 만들면 영어 이름 생성 LLM 이 나간다(netprobe 0 이어야 한다)."""
import shutil
import uuid
from pathlib import Path

import pytest
from fastapi.testclient import TestClient

from app.core.config import settings
from app.core.database import Base, engine, SessionLocal
from app.main import app
from tests._safety_guards import safe_drop_all, safe_rmtree


@pytest.fixture(autouse=True)
def _setup_db():
    Base.metadata.create_all(engine)
    with TestClient(app):
        pass
    yield
    safe_drop_all(engine, Base.metadata)
    proj_dir = Path(settings.projects_dir)
    if proj_dir.exists():
        safe_rmtree(proj_dir)


@pytest.fixture()
def client():
    with TestClient(app, raise_server_exceptions=False) as c:
        yield c


def _login(client, who="admin"):
    pw = {"admin": "admin123", "creator": "creator123"}[who]
    r = client.post("/api/v1/auth/login", json={"username": who, "password": pw})
    assert r.status_code == 200, r.text


def _project(_client=None) -> str:
    """★DB 에 직접 심는다 — `POST /projects/` 는 영어 이름 생성 LLM 을 타서 netprobe 에 바깥 호출이 잡힌다(Codex 재리뷰 BLOCK)."""
    from datetime import datetime, timezone

    from app.models.catalog import ProjectRegistry, UserAccount
    db = SessionLocal()
    try:
        admin = db.query(UserAccount).filter_by(username="admin").first()
        assert admin is not None
        now = datetime.now(timezone.utc).isoformat()
        pid = str(uuid.uuid4())
        db.add(ProjectRegistry(id=pid, name="P", name_en="P", description="d", status="active",
                               created_by=admin.id, created_at=now, updated_at=now))
        db.commit()
        Path(settings.projects_dir, pid).mkdir(parents=True, exist_ok=True)
        return pid
    finally:
        db.close()


def _episode(project_id: str) -> str:
    from app.models.project import Episode
    db = SessionLocal()
    try:
        from datetime import datetime, timezone
        now = datetime.now(timezone.utc).isoformat()
        eid = uuid.uuid4().hex
        db.add(Episode(id=eid, project_id=project_id, episode_number=1, title="t",
                       source_filename="s.pdf", source_path="s.pdf", created_at=now, updated_at=now))
        db.commit()
        return eid
    finally:
        db.close()


class TestVerdictsRoute:
    def test_member_gets_200_with_the_right_call_shape(self, client):
        _login(client); pid = _project(client); eid = _episode(pid)
        r = client.get("/api/v1/grounding-fidelity/verdicts", params={"project_id": pid, "episode_id": eid})
        assert r.status_code == 200, r.text
        assert r.json()["rows"] == []

    def test_a_non_member_gets_403(self, client):
        _login(client); pid = _project(client); eid = _episode(pid)
        client.post("/api/v1/auth/logout")
        _login(client, "creator")
        r = client.get("/api/v1/grounding-fidelity/verdicts", params={"project_id": pid, "episode_id": eid})
        assert r.status_code == 403, r.text

    def test_an_episode_of_another_project_is_404(self, client):
        _login(client); pid = _project(client); other = _project(client); eid_other = _episode(other)
        r = client.get("/api/v1/grounding-fidelity/verdicts", params={"project_id": pid, "episode_id": eid_other})
        assert r.status_code == 404, r.text


class TestTraversalIsRefused:
    @pytest.mark.parametrize("route", ["review-page", "photo"])
    def test_a_path_in_episode_id_cannot_leave_the_project(self, client, route):
        _login(client); pid = _project(client); other = _project(client); eid_other = _episode(other)
        # ★다른 프로젝트의 CP 를 이 프로젝트 권한으로 읽으려는 값 — episode 소속 검사에서 404 로 선다
        bad = f"../../../{other}/checkpoints/episodes/{eid_other}"
        params = {"project_id": pid, "episode_id": bad}
        if route == "photo":
            params["key"] = "deadbeef"
        r = client.get(f"/api/v1/grounding-fidelity/{route}", params=params)
        assert r.status_code == 404, r.text

    def test_frozen_refuses_a_resolved_path_outside_the_project(self, tmp_path, monkeypatch):
        """★DB 검사 뒤의 두 번째 문 — 경로 자체가 프로젝트 디렉토리 밖이면 읽지 않는다."""
        from app.api.v1 import grounding_fidelity as api
        from app.core.errors import AppError
        monkeypatch.setattr(settings, "projects_dir", str(tmp_path / "projects"))
        other = tmp_path / "projects" / "B" / "checkpoints" / "episodes" / "E" / "reference_acquisition"
        other.mkdir(parents=True); (other / "manifest.json").write_text("{}", encoding="utf-8")
        with pytest.raises(AppError) as exc:
            api._frozen("A", "../B/checkpoints/episodes/E")
        assert exc.value.status_code == 404
