"""GET /movies — lista i szczegóły filmów."""
from __future__ import annotations

import uuid
from typing import Annotated

from fastapi import APIRouter, Depends, HTTPException, Query
from sqlalchemy import exists, func, select
from sqlalchemy.orm import Session

from app.api.schemas import (
    ExternalRefOut,
    MovieChapterOut,
    MovieListOut,
    MovieOut,
    PerformerOut,
    PlaybackSourceOut,
    StudioOut,
    TagOut,
)
from app.auth import require_api_key
from app.db import get_session
from app.models.movie import (
    Movie,
    MovieChapter,
    MovieExternalRef,
    MoviePerformer,
    MovieTag,
)
from app.api.device import LEGACY_DEVICE, get_device_id
from app.models.favorite_movie import FavoriteMovie
from app.models.movie_playback_source import MoviePlaybackSource
from app.models.performer import Performer
from app.models.play_progress import MoviePlayProgress
from app.models.source import Source
from app.models.studio import Studio
from app.models.tag import Tag

router = APIRouter(prefix="/movies", tags=["movies"], dependencies=[Depends(require_api_key)])

_VALID_SORTS = {"created_at", "release_year", "release_date", "title", "rating"}


def _split_csv(raw: str | None) -> list[str]:
    if not raw:
        return []
    return [s.strip() for s in raw.split(",") if s.strip()]


@router.get("", response_model=MovieListOut)
def list_movies(
    session: Annotated[Session, Depends(get_session)],
    device_id: Annotated[str, Depends(get_device_id)],
    q: str | None = Query(default=None, description="Title search (trgm)"),
    studio_slugs: str | None = Query(default=None, description="Comma-separated studio slugs (OR)"),
    tags: str | None = Query(default=None, description="Comma-separated tag slugs (AND)"),
    performer_ids: str | None = Query(default=None, description="Comma-separated performer UUIDs (AND)"),
    year_from: int | None = Query(default=None, ge=1900, le=2100),
    year_to: int | None = Query(default=None, ge=1900, le=2100),
    has_playback: bool | None = Query(default=None),
    sort: str = Query(default="created_at"),
    page: int = Query(default=1, ge=1),
    per_page: int = Query(default=50, ge=1, le=200),
) -> MovieListOut:
    if sort not in _VALID_SORTS:
        raise HTTPException(status_code=400, detail=f"sort must be one of {sorted(_VALID_SORTS)}")

    base = select(Movie)

    if q:
        base = base.where(Movie.title_normalized.ilike(f"%{q.lower()}%"))

    studio_slug_list = _split_csv(studio_slugs)
    if studio_slug_list:
        base = base.where(
            Movie.studio_id.in_(select(Studio.id).where(Studio.slug.in_(studio_slug_list)))
        )

    for slug in _split_csv(tags):
        base = base.where(
            exists(
                select(1).select_from(MovieTag).join(Tag, Tag.id == MovieTag.tag_id)
                .where(MovieTag.movie_id == Movie.id, Tag.slug == slug)
            )
        )

    perf_id_strings = _split_csv(performer_ids)
    if perf_id_strings:
        try:
            perf_ids = [uuid.UUID(s) for s in perf_id_strings]
        except ValueError as e:
            raise HTTPException(status_code=400, detail=f"invalid performer UUID: {e}") from e
        for pid in perf_ids:
            base = base.where(
                exists(
                    select(1).select_from(MoviePerformer).where(
                        MoviePerformer.movie_id == Movie.id,
                        MoviePerformer.performer_id == pid,
                    )
                )
            )

    if year_from is not None:
        base = base.where(Movie.release_year >= year_from)
    if year_to is not None:
        base = base.where(Movie.release_year <= year_to)

    if has_playback is True:
        base = base.where(
            exists(
                select(1).where(
                    MoviePlaybackSource.movie_id == Movie.id,
                    MoviePlaybackSource.dead_at.is_(None),
                )
            )
        )

    total = session.execute(
        select(func.count()).select_from(base.subquery())
    ).scalar_one()

    if sort == "created_at":
        base = base.order_by(Movie.created_at.desc())
    elif sort == "release_year":
        base = base.order_by(Movie.release_year.desc().nulls_last(), Movie.created_at.desc())
    elif sort == "release_date":
        base = base.order_by(Movie.release_date.desc().nulls_last(), Movie.created_at.desc())
    elif sort == "title":
        base = base.order_by(Movie.title_normalized.asc())
    elif sort == "rating":
        base = base.order_by(Movie.rating.desc().nulls_last(), Movie.created_at.desc())

    base = base.limit(per_page).offset((page - 1) * per_page)

    movies = session.execute(base).scalars().all()
    items = [_movie_to_out(session, m, device_id=device_id) for m in movies]

    return MovieListOut(items=items, total=total, page=page, per_page=per_page)


# Movie playback origin policy — module-level (kiedyś było inline per-request
# definition, code-review #19 — perf hit + dorosły kod).
# Ranking ustalony ad-hoc 2026-05-09 (extract_stream_from_hoster na 5 sample
# random per origin).
_MOVIE_PREFERRED_ORIGINS = (
    "mangoporn:luluvid",  # KVS, działa
    "mangoporn:mixdrop",  # po domain fix może działać
    "mangoporn:voe",      # czasem yt-dlp łapie
    "mangoporn",
    "streamporn",
    "pandamovies",
)
# File hosters które NIGDY nie dadzą się stream-extract bez premium account —
# odfiltrowywane całkowicie (zaśmiecały listę watch options, bug-report
# 2026-05-15). Streamtape przywrócony 2026-05-15 — ma dedicated extractor,
# ~5% URLów żyje.
_MOVIE_DROP_ORIGINS = frozenset({
    "mangoporn:rapidgator",
    "mangoporn:nitroflare",
    "mangoporn:frdl",
})
# Raw landing origins ukrywane gdy są sub-hosters (zob. komentarz w get_movie).
_MOVIE_LANDING_HIDE = frozenset({"mangoporn", "pandamovies", "streamporn"})


def _movie_origin_priority(origin: str) -> int:
    try:
        return _MOVIE_PREFERRED_ORIGINS.index(origin)
    except ValueError:
        return 500  # neutralne (paradisehill, mangoporn:* nieklasyfikowane)


@router.get("/{movie_id}", response_model=MovieOut)
def get_movie(
    movie_id: uuid.UUID,
    session: Annotated[Session, Depends(get_session)],
    device_id: Annotated[str, Depends(get_device_id)],
) -> MovieOut:
    movie = session.get(Movie, movie_id)
    if movie is None:
        raise HTTPException(status_code=404, detail="movie not found")
    return _movie_to_out(session, movie, device_id=device_id)


def _movie_to_out(session: Session, movie: Movie, *, device_id: str = LEGACY_DEVICE) -> MovieOut:
    studio_out: StudioOut | None = None
    if movie.studio_id:
        studio = session.get(Studio, movie.studio_id)
        if studio is not None:
            studio_out = StudioOut.model_validate(studio)

    performer_rows = session.execute(
        select(Performer, MoviePerformer.as_alias)
        .join(MoviePerformer, MoviePerformer.performer_id == Performer.id)
        .where(MoviePerformer.movie_id == movie.id)
        .order_by(MoviePerformer.position.asc().nulls_last())
    ).all()
    performers = [
        PerformerOut(
            id=p.id,
            canonical_name=p.canonical_name,
            slug=p.slug,
            gender=p.gender.value if p.gender else None,
            as_alias=alias,
        )
        for p, alias in performer_rows
    ]

    tag_rows = session.execute(
        select(Tag).join(MovieTag, MovieTag.tag_id == Tag.id)
        .where(MovieTag.movie_id == movie.id)
        .order_by(Tag.name.asc())
    ).scalars().all()
    tags = [TagOut.model_validate(t) for t in tag_rows]

    chapter_rows = session.execute(
        select(MovieChapter).where(MovieChapter.movie_id == movie.id)
        .order_by(MovieChapter.chapter_index.asc())
    ).scalars().all()
    chapters = [MovieChapterOut.model_validate(c) for c in chapter_rows]

    ref_rows = session.execute(
        select(MovieExternalRef, Source.name)
        .join(Source, Source.id == MovieExternalRef.source_id)
        .where(MovieExternalRef.movie_id == movie.id)
    ).all()
    external_refs = [
        ExternalRefOut(
            source=name,
            external_id=ref.external_id,
            url=ref.url,
            last_seen=ref.last_seen,
        )
        for ref, name in ref_rows
    ]

    pb_rows = session.execute(
        select(MoviePlaybackSource)
        .where(MoviePlaybackSource.movie_id == movie.id)
        .where(MoviePlaybackSource.dead_at.is_(None))
        .order_by(MoviePlaybackSource.created_at.desc())
    ).scalars().all()
    pb_rows = [p for p in pb_rows if p.origin not in _MOVIE_DROP_ORIGINS]
    # Bug-report 2026-05-16: raw landing origins (`mangoporn`/`pandamovies`/
    # `streamporn` BEZ `:host`) otwierały WebView z reklamami pełnoekranowymi
    # i myliły usera. Ukrywamy raw landing GDY ten sam movie ma co najmniej
    # jeden sub-host entry (origin zawiera `:`). Jeśli movie nie ma sub-hosters
    # (bo theme HTML się zmienił lub regex nie złapał), zostawiamy landing jako
    # last-resort.
    has_subhost = any(":" in p.origin for p in pb_rows)
    if has_subhost:
        pb_rows = [p for p in pb_rows if p.origin not in _MOVIE_LANDING_HIDE]
    pb_rows = sorted(pb_rows, key=lambda p: _movie_origin_priority(p.origin))
    # Dedup po realnym celu: 3 mirrory dooplay (mangoporn/pandamovies/streamporn)
    # zapisują TEN SAM embed hostera jako osobne wiersze (np. luluvid/e/X z każdego
    # mirrora) → film miał ~100 linków, z czego ~połowa to ten sam film (bug-report
    # 41ca1fa4). Zostawiamy pierwszy = najwyższy priorytet (sort wyżej).
    seen_targets: set[str] = set()
    deduped: list[MoviePlaybackSource] = []
    for p in pb_rows:
        target = (p.embed_url or p.stream_url or p.page_url or "").strip()
        if target and target in seen_targets:
            continue
        if target:
            seen_targets.add(target)
        deduped.append(p)
    pb_rows = deduped
    playback_sources = [PlaybackSourceOut.model_validate(p) for p in pb_rows]

    is_fav = session.get(FavoriteMovie, (device_id, movie.id)) is not None
    progress = session.get(MoviePlayProgress, (device_id, movie.id))

    return MovieOut(
        id=movie.id,
        title=movie.title,
        slug=movie.slug,
        release_year=movie.release_year,
        release_date=movie.release_date,
        duration_sec=movie.duration_sec,
        description=movie.description,
        director=movie.director,
        country=movie.country,
        rating=movie.rating,
        poster_url=movie.poster_url,
        backdrop_url=movie.backdrop_url,
        studio=studio_out,
        performers=performers,
        tags=tags,
        chapters=chapters,
        external_refs=external_refs,
        playback_sources=playback_sources,
        created_at=movie.created_at,
        is_favorite=is_fav,
        last_played_at=progress.last_played_at if progress else None,
        finished=progress.finished if progress else False,
        position_sec=progress.position_sec if progress else 0,
    )
