"""FastAPI proxy entrypoint — the guarding proxy of charter §4.

Skeleton: a health check plus the five role endpoints. Each role endpoint now
validates the posted canon log against the contract (charter §11) before doing
anything else — an invalid log is rejected with 422 and never reaches a prompt.
`/dm/narrate` is fully wired: prompt routing, the Ollama model call, and
call-log logging. The other four roles remain stubs until they get the same
treatment.
"""

from fastapi import Depends, FastAPI, HTTPException
from fastapi.exceptions import RequestValidationError
from fastapi.responses import JSONResponse
from pydantic import BaseModel

from .canon_log import validate_canon_log
from .narrate import run as narrate_run
from .ollama_client import ModelError

app = FastAPI(title="coc-rpg proxy", version="0.0.1")


@app.exception_handler(RequestValidationError)
async def unify_validation_errors(request, exc: RequestValidationError) -> JSONResponse:
    """Reshape pydantic body-validation failures into the same envelope as a
    canon-log schema failure, so the client parses ONE 422 shape (charter §11):
    {"detail": {"canon_log_errors": [...]}}.
    """
    errors = [f"{'.'.join(str(loc) for loc in e['loc'])}: {e['msg']}" for e in exc.errors()]
    return JSONResponse(status_code=422, content={"detail": {"canon_log_errors": errors}})


class TurnRequest(BaseModel):
    canon_log: dict


def valid_turn(req: TurnRequest) -> TurnRequest:
    """Shared dependency: reject any request whose canon log breaks the contract."""
    errors = validate_canon_log(req.canon_log)
    if errors:
        raise HTTPException(status_code=422, detail={"canon_log_errors": errors})
    return req


@app.get("/health")
def health() -> dict:
    """Liveness probe for compose / fly.io."""
    return {"status": "ok"}


# ── Role endpoints (charter §4) ──────────────────────────────────────────────
# The client knows these paths and nothing about which model or prompt serves
# them. Bodies are validated against the canon log contract. /dm/narrate is
# fully wired (routing + model call + logging); the other four roles remain
# stubs until prompt routing, model selection, and logging land for them too.


@app.post("/dm/narrate")
def narrate(req: TurnRequest = Depends(valid_turn)) -> dict:
    try:
        return {"prose": narrate_run(req.canon_log)}
    except ModelError as exc:
        raise HTTPException(status_code=502, detail={"model_error": str(exc)})


@app.post("/dm/adjudicate")
def adjudicate(req: TurnRequest = Depends(valid_turn)) -> dict:
    return {"detail": "not implemented"}


@app.post("/dm/improvise")
def improvise(req: TurnRequest = Depends(valid_turn)) -> dict:
    return {"detail": "not implemented"}


@app.post("/npc/speak")
def npc_speak(req: TurnRequest = Depends(valid_turn)) -> dict:
    return {"detail": "not implemented"}


@app.post("/party/banter")
def banter(req: TurnRequest = Depends(valid_turn)) -> dict:
    return {"detail": "not implemented"}
