"""Video repository for CRUD operations on the videos table.

Handles the distinction between video_id (natural key, e.g. YouTube ID)
and id (UUID primary key). Supports progressive enrichment via upsert().
"""

from __future__ import annotations

import uuid
from typing import TYPE_CHECKING, Any

if TYPE_CHECKING:
    from claudetube.db.connection import Database


class VideoRepository:
    """Repository for video metadata operations.

    The videos table uses:
    - id: UUID primary key (generated by this repo)
    - video_id: Natural key (YouTube ID, Vimeo ID, etc.)
    - domain: Required (e.g., 'youtube', 'vimeo')
    - cache_path: Required path to video cache directory

    Progressive enrichment is supported via upsert(), which fills NULL
    fields without overwriting existing data.
    """

    def __init__(self, db: Database) -> None:
        """Initialize with a Database instance.

        Args:
            db: Database connection wrapper.
        """
        self.db = db

    def insert(
        self,
        video_id: str,
        domain: str,
        cache_path: str,
        *,
        channel: str | None = None,
        playlist: str | None = None,
        url: str | None = None,
        title: str | None = None,
        duration: float | None = None,
        duration_string: str | None = None,
        uploader: str | None = None,
        channel_name: str | None = None,
        upload_date: str | None = None,
        description: str | None = None,
        language: str | None = None,
        view_count: int | None = None,
        like_count: int | None = None,
        source_type: str = "url",
    ) -> str:
        """Insert a new video record.

        Args:
            video_id: Natural key (e.g., YouTube video ID).
            domain: Video source domain (e.g., 'youtube', 'vimeo').
            cache_path: Path to the video's cache directory.
            channel: Optional channel identifier (path-safe).
            playlist: Optional playlist identifier.
            url: Original video URL.
            title: Video title.
            duration: Duration in seconds.
            duration_string: Human-readable duration (e.g., "10:30").
            uploader: Uploader name/ID.
            channel_name: Display name of channel.
            upload_date: Upload date string.
            description: Video description.
            language: Primary language code.
            view_count: Number of views.
            like_count: Number of likes.
            source_type: 'url' or 'local' (default: 'url').

        Returns:
            The generated UUID for the new video record.

        Raises:
            sqlite3.IntegrityError: If video_id already exists or constraints violated.
        """
        new_id = str(uuid.uuid4())
        self.db.execute(
            """
            INSERT INTO videos (
                id, video_id, domain, channel, playlist, cache_path,
                url, title, duration, duration_string, uploader,
                channel_name, upload_date, description, language,
                view_count, like_count, source_type
            ) VALUES (
                ?, ?, ?, ?, ?, ?,
                ?, ?, ?, ?, ?,
                ?, ?, ?, ?,
                ?, ?, ?
            )
            """,
            (
                new_id,
                video_id,
                domain,
                channel,
                playlist,
                cache_path,
                url,
                title,
                duration,
                duration_string,
                uploader,
                channel_name,
                upload_date,
                description,
                language,
                view_count,
                like_count,
                source_type,
            ),
        )
        self.db.commit()
        return new_id

    def upsert(
        self,
        video_id: str,
        domain: str,
        cache_path: str,
        **metadata: Any,
    ) -> str:
        """Insert or update a video, filling NULL fields without overwriting.

        This is the key method for progressive enrichment. When a video
        already exists, only NULL fields are updated with new values.
        Existing non-NULL values are preserved.

        Args:
            video_id: Natural key (e.g., YouTube video ID).
            domain: Video source domain.
            cache_path: Path to the video's cache directory.
            **metadata: Optional fields to set (channel, title, etc.).

        Returns:
            The UUID for the video (existing or newly generated).
        """
        # Check if video exists
        existing = self.get_by_video_id(video_id)

        if existing is None:
            # Insert new record
            return self.insert(video_id, domain, cache_path, **metadata)

        # Update existing record, only filling NULL fields
        existing_id = existing["id"]
        update_fields = []
        update_values = []

        # Fields that can be progressively enriched
        enrichable = [
            "channel",
            "playlist",
            "url",
            "title",
            "duration",
            "duration_string",
            "uploader",
            "channel_name",
            "upload_date",
            "description",
            "language",
            "view_count",
            "like_count",
        ]

        for field in enrichable:
            if field in metadata and metadata[field] is not None:
                # Use COALESCE to only update if current value is NULL
                update_fields.append(f"{field} = COALESCE({field}, ?)")
                update_values.append(metadata[field])

        if update_fields:
            update_fields.append("updated_at = datetime('now')")
            sql = f"UPDATE videos SET {', '.join(update_fields)} WHERE id = ?"
            update_values.append(existing_id)
            self.db.execute(sql, tuple(update_values))
            self.db.commit()

        return existing_id

    def get_by_video_id(self, video_id: str) -> dict[str, Any] | None:
        """Get a video by its natural key (video_id).

        Args:
            video_id: The natural key (e.g., YouTube video ID).

        Returns:
            Dict with video data, or None if not found.
        """
        cursor = self.db.execute(
            "SELECT * FROM videos WHERE video_id = ?",
            (video_id,),
        )
        row = cursor.fetchone()
        return dict(row) if row else None

    def get_by_uuid(self, uuid_: str) -> dict[str, Any] | None:
        """Get a video by its UUID primary key.

        Args:
            uuid_: The UUID primary key.

        Returns:
            Dict with video data, or None if not found.
        """
        cursor = self.db.execute(
            "SELECT * FROM videos WHERE id = ?",
            (uuid_,),
        )
        row = cursor.fetchone()
        return dict(row) if row else None

    def resolve_path(self, video_id: str) -> str | None:
        """Get the cache_path for a video by its natural key.

        This is a convenience method for looking up where a video's
        files are stored without fetching the full record.

        Args:
            video_id: The natural key (e.g., YouTube video ID).

        Returns:
            The cache_path string, or None if video not found.
        """
        cursor = self.db.execute(
            "SELECT cache_path FROM videos WHERE video_id = ?",
            (video_id,),
        )
        row = cursor.fetchone()
        return row["cache_path"] if row else None

    def list_all(self) -> list[dict[str, Any]]:
        """List all videos.

        Returns:
            List of dicts with video data, ordered by created_at desc.
        """
        cursor = self.db.execute("SELECT * FROM videos ORDER BY created_at DESC")
        return [dict(row) for row in cursor.fetchall()]

    def search_fts(self, query: str) -> list[dict[str, Any]]:
        """Search videos using full-text search.

        Searches across title, description, and channel_name fields
        via the videos_fts virtual table.

        Args:
            query: Search query string. Supports FTS5 syntax.

        Returns:
            List of matching videos with rank, ordered by relevance.
        """
        # Escape special FTS5 characters for safety
        # Simple approach: wrap each word in double quotes for phrase matching
        escaped_query = self._escape_fts_query(query)

        cursor = self.db.execute(
            """
            SELECT v.*, rank
            FROM videos_fts
            JOIN videos v ON videos_fts.rowid = v.rowid
            WHERE videos_fts MATCH ?
            ORDER BY rank
            """,
            (escaped_query,),
        )
        return [dict(row) for row in cursor.fetchall()]

    def update_cache_path(self, video_id: str, cache_path: str) -> bool:
        """Update the cache_path for a video by its natural key.

        Used during migration from flat to hierarchical cache paths.

        Args:
            video_id: The natural key (e.g., YouTube video ID).
            cache_path: New cache path (relative to cache_base).

        Returns:
            True if a video was updated, False if not found.
        """
        cursor = self.db.execute(
            "UPDATE videos SET cache_path = ?, updated_at = datetime('now') WHERE video_id = ?",
            (cache_path, video_id),
        )
        self.db.commit()
        return cursor.rowcount > 0

    def delete(self, video_id: str) -> bool:
        """Delete a video by its natural key.

        Note: Due to ON DELETE CASCADE, this will also delete all
        related records (audio_tracks, transcriptions, scenes, etc.).

        Args:
            video_id: The natural key (e.g., YouTube video ID).

        Returns:
            True if a video was deleted, False if not found.
        """
        cursor = self.db.execute(
            "DELETE FROM videos WHERE video_id = ?",
            (video_id,),
        )
        self.db.commit()
        return cursor.rowcount > 0

    def _escape_fts_query(self, query: str) -> str:
        """Escape a query string for FTS5 MATCH syntax.

        Wraps terms in double quotes to prevent FTS5 syntax errors
        from special characters like colons, parentheses, etc.

        Args:
            query: Raw search query.

        Returns:
            Escaped query safe for FTS5 MATCH.
        """
        # Split on whitespace, quote each term, join with spaces
        # This converts "foo bar:baz" to '"foo" "bar:baz"'
        terms = query.split()
        if not terms:
            return '""'
        # Escape any embedded double quotes
        escaped_terms = ['"' + term.replace('"', '""') + '"' for term in terms]
        return " ".join(escaped_terms)
