import json
from pathlib import Path

import pytest
from fastapi.testclient import TestClient

import app.narrate as narrate
from app.main import app

client = TestClient(app)
VALID_LOG = json.load(open(Path(__file__).parent / "fixtures" / "canon_log_valid.json"))

ROLE_ENDPOINTS = [
    "/dm/narrate", "/dm/adjudicate", "/dm/improvise",
    "/npc/speak", "/party/banter",
]


@pytest.fixture(autouse=True)
def _stub_narrate_model_call(monkeypatch):
    # /dm/narrate is wired to a real Ollama call (Task 7). These tests exercise
    # canon-log validation across ALL role endpoints, not model behavior, so
    # stub the model boundary — keeps the default suite hermetic (no network).
    monkeypatch.setattr(narrate.ollama_client, "chat", lambda *a, **k: "stubbed prose")
    monkeypatch.setattr(narrate.call_log, "record", lambda **kw: None)


def test_health_ok():
    assert client.get("/health").json() == {"status": "ok"}


def test_every_role_endpoint_accepts_a_valid_canon_log():
    for path in ROLE_ENDPOINTS:
        r = client.post(path, json={"canon_log": VALID_LOG})
        assert r.status_code == 200, f"{path} rejected a valid log: {r.text}"


def test_every_role_endpoint_rejects_an_invalid_canon_log():
    bad = json.loads(json.dumps(VALID_LOG))
    bad["player"]["luck"] = 5  # §7 leak
    for path in ROLE_ENDPOINTS:
        r = client.post(path, json={"canon_log": bad})
        assert r.status_code == 422, f"{path} accepted an invalid log"
        assert "canon_log_errors" in r.json()["detail"]


def test_missing_canon_log_is_a_422():
    r = client.post("/dm/narrate", json={})
    assert r.status_code == 422
    assert "canon_log_errors" in r.json()["detail"]


def test_malformed_body_uses_the_unified_error_shape():
    # A pydantic body failure returns the SAME envelope as a schema failure,
    # so the client only ever parses one 422 shape.
    r = client.post("/dm/narrate", json={"canon_log": "not-a-dict"})
    assert r.status_code == 422
    assert "canon_log_errors" in r.json()["detail"]
