"""Watch history + continue watching.

Single-user. Mobile pingu POST /scenes/{id}/progress przy:
  - Klik Watch (position_sec=0) — wciąga scenę do recent watch
  - Powrót z MX z ACTION_RESULT (gdy włączone EXTRA_RETURN_RESULT) — z faktyczną pozycją

Continue watching rail na home: GET /watch/recent?limit=10 → top scen po last_played_at,
filtruje dead-finished (>=95% lub flag finished). Mobile pokazuje progress bar
(position_sec / duration_sec).
"""
from __future__ import annotations

import uuid
from datetime import UTC, datetime
from typing import Annotated

from fastapi import APIRouter, Depends, HTTPException, Query, status
from pydantic import BaseModel
from sqlalchemy import desc, select
from sqlalchemy.orm import Session

from app.api.device import get_device_id
from app.api.scenes import _build_scene_out
from app.api.schemas import SceneOut
from app.auth import require_api_key
from app.db import get_session
from app.models.movie import Movie
from app.models.play_progress import MoviePlayProgress, ScenePlayProgress
from app.models.scene import Scene

router = APIRouter(tags=["watch"], dependencies=[Depends(require_api_key)])


class ProgressIn(BaseModel):
    position_sec: int = 0
    duration_sec: int | None = None
    finished: bool = False


class ProgressOut(BaseModel):
    scene_id: uuid.UUID
    position_sec: int
    duration_sec: int | None
    finished: bool
    last_played_at: datetime


@router.post("/scenes/{scene_id}/progress", response_model=ProgressOut)
def upsert_progress(
    scene_id: uuid.UUID,
    body: ProgressIn,
    session: Annotated[Session, Depends(get_session)],
    device_id: Annotated[str, Depends(get_device_id)],
) -> ProgressOut:
    if session.get(Scene, scene_id) is None:
        raise HTTPException(status_code=404, detail="scene not found")

    # PG upsert — eliminuje race condition gdy mobile wysyła progress równolegle
    # (np. 2 instancje playera lub auto-save + manual save). Wcześniej `get → add →
    # commit` rzucało IntegrityError(pk_scene_play_progress) przy concurrent writes.
    from sqlalchemy.dialects.postgresql import insert as pg_insert

    now = datetime.now(UTC)
    position_sec = max(0, body.position_sec)
    finished = body.finished or (
        bool(body.duration_sec)
        and body.duration_sec > 0
        and position_sec >= int(body.duration_sec * 0.95)
    )
    stmt = (
        pg_insert(ScenePlayProgress)
        .values(
            device_id=device_id,
            scene_id=scene_id,
            position_sec=position_sec,
            duration_sec=body.duration_sec,
            finished=finished,
            last_played_at=now,
        )
        .on_conflict_do_update(
            index_elements=["device_id", "scene_id"],
            set_={
                "position_sec": position_sec,
                # duration_sec: zachowaj istniejący gdy body nie podaje
                "duration_sec": (
                    body.duration_sec
                    if body.duration_sec is not None
                    else ScenePlayProgress.duration_sec
                ),
                "finished": finished,
                "last_played_at": now,
            },
        )
    )
    session.execute(stmt)
    session.commit()
    row = session.get(ScenePlayProgress, (device_id, scene_id))
    assert row is not None
    return ProgressOut(
        scene_id=scene_id,
        position_sec=row.position_sec,
        duration_sec=row.duration_sec,
        finished=row.finished,
        last_played_at=row.last_played_at,
    )


@router.delete(
    "/scenes/{scene_id}/progress",
    status_code=status.HTTP_204_NO_CONTENT,
)
def remove_progress(
    scene_id: uuid.UUID,
    session: Annotated[Session, Depends(get_session)],
    device_id: Annotated[str, Depends(get_device_id)],
) -> None:
    row = session.get(ScenePlayProgress, (device_id, scene_id))
    if row is None:
        return
    session.delete(row)
    session.commit()


# ---- Movie progress (mirror scen) ------------------------------------------


class MovieProgressOut(BaseModel):
    movie_id: uuid.UUID
    position_sec: int
    duration_sec: int | None
    finished: bool
    last_played_at: datetime


@router.post("/movies/{movie_id}/progress", response_model=MovieProgressOut)
def upsert_movie_progress(
    movie_id: uuid.UUID,
    body: ProgressIn,
    session: Annotated[Session, Depends(get_session)],
    device_id: Annotated[str, Depends(get_device_id)],
) -> MovieProgressOut:
    """Mirror upsert_progress dla filmów (bug-report b207ff17 2026-05-26 —
    "przydałoby się oznaczenie filmów już obejrzanych")."""
    if session.get(Movie, movie_id) is None:
        raise HTTPException(status_code=404, detail="movie not found")

    from sqlalchemy.dialects.postgresql import insert as pg_insert

    now = datetime.now(UTC)
    position_sec = max(0, body.position_sec)
    finished = body.finished or (
        bool(body.duration_sec)
        and body.duration_sec > 0
        and position_sec >= int(body.duration_sec * 0.95)
    )
    stmt = (
        pg_insert(MoviePlayProgress)
        .values(
            device_id=device_id,
            movie_id=movie_id,
            position_sec=position_sec,
            duration_sec=body.duration_sec,
            finished=finished,
            last_played_at=now,
        )
        .on_conflict_do_update(
            index_elements=["device_id", "movie_id"],
            set_={
                "position_sec": position_sec,
                "duration_sec": (
                    body.duration_sec
                    if body.duration_sec is not None
                    else MoviePlayProgress.duration_sec
                ),
                "finished": finished,
                "last_played_at": now,
            },
        )
    )
    session.execute(stmt)
    session.commit()
    row = session.get(MoviePlayProgress, (device_id, movie_id))
    assert row is not None
    return MovieProgressOut(
        movie_id=movie_id,
        position_sec=row.position_sec,
        duration_sec=row.duration_sec,
        finished=row.finished,
        last_played_at=row.last_played_at,
    )


@router.delete(
    "/movies/{movie_id}/progress",
    status_code=status.HTTP_204_NO_CONTENT,
)
def remove_movie_progress(
    movie_id: uuid.UUID,
    session: Annotated[Session, Depends(get_session)],
    device_id: Annotated[str, Depends(get_device_id)],
) -> None:
    row = session.get(MoviePlayProgress, (device_id, movie_id))
    if row is None:
        return
    session.delete(row)
    session.commit()


class WatchEntry(BaseModel):
    scene: SceneOut
    position_sec: int
    duration_sec: int | None
    finished: bool
    last_played_at: datetime


class WatchListOut(BaseModel):
    items: list[WatchEntry]


@router.get("/watch/recent", response_model=WatchListOut)
def list_recent(
    session: Annotated[Session, Depends(get_session)],
    device_id: Annotated[str, Depends(get_device_id)],
    limit: int = Query(default=10, ge=1, le=50),
    include_finished: bool = Query(default=False),
) -> WatchListOut:
    """Top-N scen po last_played_at desc. Domyślnie pomija sceny finished
    (user nie chce widzieć już dograne w continue rail)."""
    stmt = (
        select(ScenePlayProgress, Scene)
        .join(Scene, Scene.id == ScenePlayProgress.scene_id)
        .where(ScenePlayProgress.device_id == device_id)
        .order_by(desc(ScenePlayProgress.last_played_at))
        .limit(limit)
    )
    if not include_finished:
        stmt = stmt.where(ScenePlayProgress.finished.is_(False))

    items: list[WatchEntry] = []
    for prog, scene in session.execute(stmt).all():
        items.append(
            WatchEntry(
                scene=_build_scene_out(session, scene, device_id=device_id),
                position_sec=prog.position_sec,
                duration_sec=prog.duration_sec,
                finished=prog.finished,
                last_played_at=prog.last_played_at,
            )
        )
    return WatchListOut(items=items)
