"""The conversation API (M4 mechanics, M6 scoping): a ball's comments
(attachments upload/serve is routers.attachments). Comments interleave
with touches into the rally timeline (plan §3.5). The GET list is SCOPED
for the requesting viewer (§4.3: "comments are scoped, not just
anonymized") — invisible-node stretches arrive as collapsed counts, never
bodies; the create/patch responses stay full CommentOut because the
author's own comment is always in their own scope. Sync def (react.md:
sync for DB routes). Auth: the default-deny middleware gates every route
here; author_id is stamped from the session identity, never accepted from
the client (ref §2.7)."""
from __future__ import annotations

import uuid

from fastapi import APIRouter, Depends, Request
from fastapi.responses import JSONResponse
from sqlalchemy.orm import Session

from ..auth import current_player
from ..db import get_db
from ..models import Ball, Comment, Court
from ..schemas import CommentCreate, CommentEntryOut, CommentOut, CommentPatch
from ..services import conversation as conversation_service
from ..services import projection as projection_service
from ..services import viewer as viewer_service

router = APIRouter()


def _error_response(err: conversation_service.ConversationError) -> JSONResponse:
    return JSONResponse(
        status_code=err.status_code,
        content={
            "error_code": err.error_code,
            "summary": err.summary,
            "detail": err.detail,
        },
    )


def _ball_not_found(ball_id) -> JSONResponse:
    return JSONResponse(
        status_code=404,
        content={"error_code": "BALL_NOT_FOUND", "summary": f"No ball {ball_id}"},
    )


def _comment_not_found(comment_id) -> JSONResponse:
    return JSONResponse(
        status_code=404,
        content={"error_code": "COMMENT_NOT_FOUND", "summary": f"No comment {comment_id}"},
    )


@router.get("/api/balls/{ball_id}/comments", response_model=list[CommentEntryOut])
def list_comments(ball_id: uuid.UUID, request: Request, db: Session = Depends(get_db)):
    ball = db.get(Ball, ball_id)
    if ball is None:
        return _ball_not_found(ball_id)
    court = db.get(Court, ball.court_id)
    viewer = viewer_service.resolve_viewer(request, db)
    lens = projection_service.build_lens(db, court.match_id, viewer)
    return projection_service.project_comments(
        lens, conversation_service.list_comments(db, ball_id)
    )


@router.post("/api/balls/{ball_id}/comments", response_model=CommentOut, status_code=201)
def create_comment(
    ball_id: uuid.UUID, payload: CommentCreate, request: Request, db: Session = Depends(get_db)
):
    ball = db.get(Ball, ball_id)
    if ball is None:
        return _ball_not_found(ball_id)
    # Author is stamped from the SESSION identity (ref §2.7 port note: the
    # client never says who it is). None is allowed — mirrors create_ball.
    author = current_player(request)
    try:
        return conversation_service.create_comment(
            db,
            ball,
            author,
            payload.body,
            created_at_override=payload.created_at,
            attachments=[a.model_dump() for a in payload.attachments],
        )
    except conversation_service.ConversationError as err:
        return _error_response(err)


@router.patch("/api/comments/{comment_id}", response_model=CommentOut)
def update_comment(
    comment_id: uuid.UUID, payload: CommentPatch, request: Request, db: Session = Depends(get_db)
):
    comment = db.get(Comment, comment_id)
    if comment is None:
        return _comment_not_found(comment_id)
    # Only explicitly-sent fields apply (same exclude_unset convention as
    # BallPatch) — a body-less patch that only backdates created_at must
    # not accidentally stamp edited_at.
    data = payload.model_dump(exclude_unset=True)
    actor = current_player(request)
    try:
        return conversation_service.update_comment(
            db, comment, actor, body=data.get("body"), created_at=data.get("created_at")
        )
    except conversation_service.ConversationError as err:
        return _error_response(err)


@router.delete("/api/comments/{comment_id}")
def delete_comment(comment_id: uuid.UUID, request: Request, db: Session = Depends(get_db)):
    comment = db.get(Comment, comment_id)
    if comment is None:
        return _comment_not_found(comment_id)
    actor = current_player(request)
    try:
        conversation_service.delete_comment(db, comment, actor)
    except conversation_service.ConversationError as err:
        return _error_response(err)
    return {"ok": True}
