"""Plex integration catalog + per-user watch state

Revision ID: 0044_plex_catalog
Revises: 0043_asset_source_url
Create Date: 2026-07-05

The optional Plex module's dedicated, self-contained catalog mirrored from a locally-reachable
Plex server (playback is from the local physical file; Plex is metadata only). Tables:

- plex_libraries  — mirrored library sections (movie|show), only `enabled` ones are exposed.
- plex_shows      — TV shows (poster + summary for the drill-down); weighted FTS search_vector.
- plex_seasons    — seasons, for ordering episodes.
- plex_items      — the playable LEAF (movie|episode): metadata + physical-media facts + the
                    ffprobe playability class; weighted FTS search_vector.
- plex_states     — per-user watch/in-progress state (mirrors video_states) so it works for
                    users without a Plex account.

The generated `search_vector` columns + their GIN indexes are added via raw SQL (same approach
as migration 0032), reusing the `unaccent_simple` text-search config from migration 0031.
"""
from typing import Sequence, Union

import sqlalchemy as sa
from alembic import op

revision: str = "0044_plex_catalog"
down_revision: Union[str, None] = "0043_asset_source_url"
branch_labels: Union[str, Sequence[str], None] = None
depends_on: Union[str, Sequence[str], None] = None

# Weighted document: title (A) > truncated summary (C). Same unaccent_simple config as videos.
_SEARCH_VECTOR_EXPR = (
    "setweight(to_tsvector('public.unaccent_simple', coalesce(title, '')), 'A') || "
    "setweight(to_tsvector('public.unaccent_simple', left(coalesce(summary, ''), 1000)), 'C')"
)


def _add_search_vector(table: str) -> None:
    op.execute(
        f"ALTER TABLE {table} ADD COLUMN search_vector tsvector "
        f"GENERATED ALWAYS AS ({_SEARCH_VECTOR_EXPR}) STORED"
    )
    op.execute(f"CREATE INDEX ix_{table}_search_vector ON {table} USING gin (search_vector)")


def upgrade() -> None:
    op.create_table(
        "plex_libraries",
        sa.Column("id", sa.Integer(), primary_key=True),
        sa.Column("plex_key", sa.String(length=32), nullable=False),
        sa.Column("title", sa.String(length=255), nullable=False),
        sa.Column("kind", sa.String(length=16), nullable=False),
        sa.Column("enabled", sa.Boolean(), server_default="true", nullable=False),
        sa.Column("synced_at", sa.DateTime(timezone=True), nullable=True),
        sa.Column("created_at", sa.DateTime(timezone=True), server_default=sa.func.now(), nullable=False),
        sa.Column("updated_at", sa.DateTime(timezone=True), server_default=sa.func.now(), nullable=False),
    )
    op.create_index("ix_plex_libraries_plex_key", "plex_libraries", ["plex_key"], unique=True)

    op.create_table(
        "plex_shows",
        sa.Column("id", sa.Integer(), primary_key=True),
        sa.Column("rating_key", sa.String(length=32), nullable=False),
        sa.Column("library_id", sa.Integer(), sa.ForeignKey("plex_libraries.id", ondelete="CASCADE"), nullable=False),
        sa.Column("title", sa.String(length=512), nullable=False),
        sa.Column("summary", sa.Text(), nullable=True),
        sa.Column("year", sa.Integer(), nullable=True),
        sa.Column("thumb_key", sa.String(length=512), nullable=True),
        sa.Column("art_key", sa.String(length=512), nullable=True),
        sa.Column("child_count", sa.Integer(), nullable=True),
        sa.Column("added_at", sa.DateTime(timezone=True), nullable=True),
        sa.Column("created_at", sa.DateTime(timezone=True), server_default=sa.func.now(), nullable=False),
        sa.Column("updated_at", sa.DateTime(timezone=True), server_default=sa.func.now(), nullable=False),
    )
    op.create_index("ix_plex_shows_rating_key", "plex_shows", ["rating_key"], unique=True)
    op.create_index("ix_plex_shows_library_id", "plex_shows", ["library_id"])
    op.create_index("ix_plex_shows_added_at", "plex_shows", ["added_at"])
    _add_search_vector("plex_shows")

    op.create_table(
        "plex_seasons",
        sa.Column("id", sa.Integer(), primary_key=True),
        sa.Column("rating_key", sa.String(length=32), nullable=False),
        sa.Column("show_id", sa.Integer(), sa.ForeignKey("plex_shows.id", ondelete="CASCADE"), nullable=False),
        sa.Column("title", sa.String(length=255), nullable=True),
        sa.Column("season_number", sa.Integer(), nullable=True),
        sa.Column("thumb_key", sa.String(length=512), nullable=True),
        sa.Column("created_at", sa.DateTime(timezone=True), server_default=sa.func.now(), nullable=False),
        sa.Column("updated_at", sa.DateTime(timezone=True), server_default=sa.func.now(), nullable=False),
    )
    op.create_index("ix_plex_seasons_rating_key", "plex_seasons", ["rating_key"], unique=True)
    op.create_index("ix_plex_seasons_show_id", "plex_seasons", ["show_id"])
    op.create_index("ix_plex_seasons_season_number", "plex_seasons", ["season_number"])

    op.create_table(
        "plex_items",
        sa.Column("id", sa.Integer(), primary_key=True),
        sa.Column("rating_key", sa.String(length=32), nullable=False),
        sa.Column("library_id", sa.Integer(), sa.ForeignKey("plex_libraries.id", ondelete="CASCADE"), nullable=False),
        sa.Column("kind", sa.String(length=16), nullable=False),
        sa.Column("show_id", sa.Integer(), sa.ForeignKey("plex_shows.id", ondelete="CASCADE"), nullable=True),
        sa.Column("season_id", sa.Integer(), sa.ForeignKey("plex_seasons.id", ondelete="CASCADE"), nullable=True),
        sa.Column("season_number", sa.Integer(), nullable=True),
        sa.Column("episode_number", sa.Integer(), nullable=True),
        sa.Column("title", sa.String(length=512), nullable=False),
        sa.Column("summary", sa.Text(), nullable=True),
        sa.Column("year", sa.Integer(), nullable=True),
        sa.Column("duration_s", sa.Integer(), nullable=True),
        sa.Column("thumb_key", sa.String(length=512), nullable=True),
        sa.Column("art_key", sa.String(length=512), nullable=True),
        sa.Column("cast", sa.JSON(), nullable=True),
        sa.Column("file_path", sa.Text(), nullable=True),
        sa.Column("part_key", sa.String(length=255), nullable=True),
        sa.Column("container", sa.String(length=16), nullable=True),
        sa.Column("codec_video", sa.String(length=32), nullable=True),
        sa.Column("codec_audio", sa.String(length=32), nullable=True),
        sa.Column("size_bytes", sa.BigInteger(), nullable=True),
        sa.Column("playable", sa.String(length=12), nullable=True),
        sa.Column("probed_at", sa.DateTime(timezone=True), nullable=True),
        sa.Column("markers", sa.JSON(), nullable=True),
        sa.Column("added_at", sa.DateTime(timezone=True), nullable=True),
        sa.Column("created_at", sa.DateTime(timezone=True), server_default=sa.func.now(), nullable=False),
        sa.Column("updated_at", sa.DateTime(timezone=True), server_default=sa.func.now(), nullable=False),
    )
    op.create_index("ix_plex_items_rating_key", "plex_items", ["rating_key"], unique=True)
    op.create_index("ix_plex_items_library_id", "plex_items", ["library_id"])
    op.create_index("ix_plex_items_kind", "plex_items", ["kind"])
    op.create_index("ix_plex_items_show_id", "plex_items", ["show_id"])
    op.create_index("ix_plex_items_season_id", "plex_items", ["season_id"])
    op.create_index("ix_plex_items_playable", "plex_items", ["playable"])
    op.create_index("ix_plex_items_added_at", "plex_items", ["added_at"])
    op.create_index(
        "ix_plex_items_show_order", "plex_items", ["show_id", "season_number", "episode_number"]
    )
    _add_search_vector("plex_items")

    op.create_table(
        "plex_states",
        sa.Column("id", sa.Integer(), primary_key=True),
        sa.Column("user_id", sa.Integer(), sa.ForeignKey("users.id", ondelete="CASCADE"), nullable=False),
        sa.Column("item_id", sa.Integer(), sa.ForeignKey("plex_items.id", ondelete="CASCADE"), nullable=False),
        sa.Column("status", sa.String(length=12), server_default="new", nullable=False),
        sa.Column("position_seconds", sa.Integer(), server_default="0", nullable=False),
        sa.Column("watched_at", sa.DateTime(timezone=True), nullable=True),
        sa.Column("progress_updated_at", sa.DateTime(timezone=True), nullable=True),
        sa.Column("synced_to_plex", sa.Boolean(), server_default="false", nullable=False),
        sa.Column("updated_at", sa.DateTime(timezone=True), server_default=sa.func.now(), nullable=False),
        sa.UniqueConstraint("user_id", "item_id", name="uq_plex_user_item"),
    )
    op.create_index("ix_plex_states_user_id", "plex_states", ["user_id"])
    op.create_index("ix_plex_states_item_id", "plex_states", ["item_id"])


def downgrade() -> None:
    op.drop_table("plex_states")
    op.execute("DROP INDEX IF EXISTS ix_plex_items_search_vector")
    op.drop_table("plex_items")
    op.drop_table("plex_seasons")
    op.execute("DROP INDEX IF EXISTS ix_plex_shows_search_vector")
    op.drop_table("plex_shows")
    op.drop_table("plex_libraries")
