"""SQLAlchemy models for the music index.

Storage is source-of-truth and incremental: every ingest/enrich step UPSERTs
into these tables, so re-running only updates what changed and nothing already
fetched is lost.
"""
from datetime import datetime

from sqlalchemy import (
    Boolean,
    Column,
    DateTime,
    Float,
    Integer,
    String,
    UniqueConstraint,
)
from sqlalchemy.orm import declarative_base

Base = declarative_base()

SOURCE_SPOTIFY = "spotify"
SOURCE_ITUNES = "itunes"
SOURCE_LOCAL = "local"

# mbid_status values
MB_UNRESOLVED = None   # never looked up
MB_FOUND = "found"
MB_NOTFOUND = "notfound"
MB_ERROR = "error"


class Track(Base):
    """One track as seen in one source (spotify / itunes / local).

    The same song appears as several rows (one per source); the matcher links
    them by ``match_key`` (recording MBID when known, else a normalized text
    key). ``(source, source_id)`` is the natural key used for upserts.
    """

    __tablename__ = "track"

    id = Column(Integer, primary_key=True)
    source = Column(String, nullable=False, index=True)
    source_id = Column(String, nullable=False)  # spotify id / itunes track id / local path

    title = Column(String)
    artist = Column(String)        # primary artist (display + matching)
    artists = Column(String)       # all artists, joined with ", "
    album = Column(String)
    duration_ms = Column(Integer)
    play_count = Column(Integer, default=0)
    local_path = Column(String)

    # Stable identifiers used for ID-based matching.
    isrc = Column(String, index=True)
    mbid = Column(String, index=True)          # recording MBID
    norm_key = Column(String, index=True)      # normalized "artist|title" fallback

    # Where a Spotify track came from (a track can be several at once). Used to
    # tell genuinely-saved tracks (liked / in a playlist) apart from ones only
    # surfaced by the top-tracks endpoint, and to opt those out of the library.
    liked = Column(Boolean, default=False, index=True)
    from_playlist = Column(Boolean, default=False, index=True)
    from_top = Column(Boolean, default=False, index=True)

    # Enrichment bookkeeping so the resolver only touches unresolved rows.
    mbid_status = Column(String, index=True)   # None | found | notfound | error
    mbid_source = Column(String)               # isrc | text
    updated_at = Column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow)

    __table_args__ = (
        UniqueConstraint("source", "source_id", name="uq_track_source_id"),
    )

    @property
    def match_key(self) -> str:
        """Key used to decide identity across sources: prefer the recording
        MBID, fall back to the normalized text key."""
        return self.mbid or self.norm_key

    @property
    def origin(self) -> str:
        """Human-readable Spotify origin, e.g. "liked+playlist" or "top"."""
        parts = [name for flag, name in
                 (("liked", "liked"), ("from_playlist", "playlist"), ("from_top", "top"))
                 if getattr(self, flag)]
        return "+".join(parts)


class MbCache(Base):
    """Cache of MusicBrainz lookups (positive AND negative) so the resolver is
    incremental and never repeats a query — keyed by ISRC or by a normalized
    text query."""

    __tablename__ = "mb_cache"

    query_key = Column(String, primary_key=True)  # "isrc:XXX" or "txt:artist|title|album"
    mbid = Column(String)
    score = Column(Integer)
    status = Column(String)                        # found | notfound | error
    fetched_at = Column(DateTime, default=datetime.utcnow)


class FileState(Base):
    """Per-file index state for incremental local scanning: a file is only
    re-read when its mtime/size change."""

    __tablename__ = "file_state"

    path = Column(String, primary_key=True)
    mtime = Column(Float)
    size = Column(Integer)
    indexed_at = Column(DateTime, default=datetime.utcnow)


class IngestState(Base):
    """Small key/value table for incremental-fetch bookkeeping, e.g. the newest
    liked-track timestamp or cover URLs seen on the last run."""

    __tablename__ = "ingest_state"

    key = Column(String, primary_key=True)
    value = Column(String)
    updated_at = Column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow)


class SpotifyPlaylist(Base):
    """Per-playlist sync state so unchanged playlists are skipped: Spotify's
    snapshot_id changes whenever a playlist's contents change, and cover_url
    changes when its image changes."""

    __tablename__ = "spotify_playlist"

    playlist_id = Column(String, primary_key=True)
    name = Column(String)
    snapshot_id = Column(String)
    cover_url = Column(String)
    updated_at = Column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow)
