"""The rally API (M2 mechanics, M6 projection): a ball's touch history —
PROJECTED for the requesting viewer (plan §4.3) — and the explicit pass.

The rally read collapses touches by invisible actors into counted,
labelled entries (services.projection.project_rally_entries); visible
touches keep their real actor with rep()'d targets. The pass endpoint
validates targets against the ACTING player's visibility (§4.3b, 422
TARGET_NOT_VISIBLE) and runs the receiver's autopass rule — both inside
services.touches.pass_ball.
"""
from __future__ import annotations

import uuid

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

from ..auth import current_player
from ..db import get_db
from ..models import Ball, Court
from ..schemas import BallOut
from ..services import board as board_service
from ..services import projection as projection_service
from ..services import touches as touch_service
from ..services import viewer as viewer_service

router = APIRouter()


class RallyEntryOut(BaseModel):
    """One rally timeline entry. kind is a touch kind (serve | pass |
    spike | resolve | reopen | auto) with the touch fields set, or
    "collapsed" — an invisible stretch — with count + label set (§4.3:
    "3 touches on the Bowden side"; no actors, no targets)."""

    kind: str
    seq: int | None = None
    from_player: uuid.UUID | None = None
    to_players: list[uuid.UUID] = Field(default_factory=list)
    auto: bool = False
    created_at: str | None = None
    count: int | None = None
    label: str | None = None


class RallyOut(BaseModel):
    ball_id: uuid.UUID
    touches: list[RallyEntryOut]


class PassIn(BaseModel):
    to_players: list[uuid.UUID]


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


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


@router.get("/api/balls/{ball_id}/rally", response_model=RallyOut)
def get_rally(ball_id: uuid.UUID, request: Request, db: Session = Depends(get_db)):
    ball = db.get(Ball, ball_id)
    if ball is None:
        return _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)
    touches = touch_service.rally_for(db, ball_id)
    return {
        "ball_id": ball_id,
        "touches": projection_service.project_rally_entries(lens, touches),
    }


@router.post("/api/balls/{ball_id}/pass", response_model=BallOut)
def pass_ball(
    ball_id: uuid.UUID, payload: PassIn, request: Request, db: Session = Depends(get_db)
):
    ball = db.get(Ball, ball_id)
    if ball is None:
        return _not_found(ball_id)
    actor = current_player(request)
    try:
        touch_service.pass_ball(db, ball, actor, payload.to_players)
    except board_service.BoardError as err:
        return _error_response(err)
    return BallOut.model_validate(ball)
