"""
local_db.py — Lokal SQLite database til offline brug.

Håndterer:
  - Musikbiblioteker (stier der overvåges)
  - Sange høstet fra filsystemet
  - Lokale afspilningslister (offline-projekter)
  - Synkroniseringsstatus mod API
"""

import sqlite3
import threading
from contextlib import contextmanager
from datetime import datetime, timezone
from pathlib import Path

DB_PATH = Path.home() / ".linedance" / "local.db"

_local = threading.local()


def _get_conn() -> sqlite3.Connection:
    """Returnerer en thread-lokal forbindelse."""
    if not hasattr(_local, "conn") or _local.conn is None:
        DB_PATH.parent.mkdir(parents=True, exist_ok=True)
        conn = sqlite3.connect(DB_PATH, check_same_thread=False)
        conn.row_factory = sqlite3.Row
        conn.execute("PRAGMA journal_mode=WAL")   # bedre concurrent adgang
        conn.execute("PRAGMA foreign_keys=ON")
        _local.conn = conn
    return _local.conn


@contextmanager
def get_db():
    conn = _get_conn()
    try:
        yield conn
        conn.commit()
    except Exception:
        conn.rollback()
        raise


def init_db():
    """Opret alle tabeller hvis de ikke findes."""
    with get_db() as conn:
        conn.executescript("""
            -- Musikbiblioteker der overvåges
            CREATE TABLE IF NOT EXISTS libraries (
                id              INTEGER PRIMARY KEY AUTOINCREMENT,
                path            TEXT NOT NULL UNIQUE,
                is_active       INTEGER NOT NULL DEFAULT 1,
                last_full_scan  TEXT,
                created_at      TEXT NOT NULL DEFAULT (datetime('now'))
            );

            -- Sange høstet fra filsystemet
            CREATE TABLE IF NOT EXISTS songs (
                id                  TEXT PRIMARY KEY,
                library_id          INTEGER REFERENCES libraries(id),
                local_path          TEXT NOT NULL UNIQUE,
                title               TEXT NOT NULL DEFAULT '',
                artist              TEXT NOT NULL DEFAULT '',
                album               TEXT NOT NULL DEFAULT '',
                bpm                 INTEGER NOT NULL DEFAULT 0,
                duration_sec        INTEGER NOT NULL DEFAULT 0,
                file_format         TEXT NOT NULL DEFAULT '',
                file_modified_at    TEXT NOT NULL,
                file_missing        INTEGER NOT NULL DEFAULT 0,
                api_song_id         TEXT,           -- NULL hvis ikke synkroniseret
                last_synced_at      TEXT,
                created_at          TEXT NOT NULL DEFAULT (datetime('now'))
            );

            -- Danse knyttet til en sang (kun MP3 kan skrive tags)
            CREATE TABLE IF NOT EXISTS song_dances (
                id          INTEGER PRIMARY KEY AUTOINCREMENT,
                song_id     TEXT NOT NULL REFERENCES songs(id) ON DELETE CASCADE,
                dance_name  TEXT NOT NULL,
                dance_order INTEGER NOT NULL DEFAULT 1
            );

            -- Lokale afspilningslister (offline-projekter)
            CREATE TABLE IF NOT EXISTS playlists (
                id          INTEGER PRIMARY KEY AUTOINCREMENT,
                name        TEXT NOT NULL,
                description TEXT NOT NULL DEFAULT '',
                api_project_id TEXT,               -- NULL hvis ikke synkroniseret
                last_synced_at TEXT,
                created_at  TEXT NOT NULL DEFAULT (datetime('now'))
            );

            -- Sange i en afspilningsliste
            CREATE TABLE IF NOT EXISTS playlist_songs (
                id          INTEGER PRIMARY KEY AUTOINCREMENT,
                playlist_id INTEGER NOT NULL REFERENCES playlists(id) ON DELETE CASCADE,
                song_id     TEXT NOT NULL REFERENCES songs(id),
                position    INTEGER NOT NULL,
                status      TEXT NOT NULL DEFAULT 'pending',  -- pending|playing|played|skipped
                UNIQUE(playlist_id, position)
            );

            -- Synkroniseringskø — ændringer der venter på at komme online
            CREATE TABLE IF NOT EXISTS sync_queue (
                id          INTEGER PRIMARY KEY AUTOINCREMENT,
                entity_type TEXT NOT NULL,   -- 'song'|'playlist'|'playlist_song'
                entity_id   TEXT NOT NULL,
                action      TEXT NOT NULL,   -- 'create'|'update'|'delete'
                payload     TEXT NOT NULL,   -- JSON
                created_at  TEXT NOT NULL DEFAULT (datetime('now'))
            );

            -- Indekser til hurtig søgning
            CREATE INDEX IF NOT EXISTS idx_songs_title    ON songs(title);
            CREATE INDEX IF NOT EXISTS idx_songs_artist   ON songs(artist);
            CREATE INDEX IF NOT EXISTS idx_songs_missing  ON songs(file_missing);
            CREATE INDEX IF NOT EXISTS idx_songs_library  ON songs(library_id);
            CREATE INDEX IF NOT EXISTS idx_song_dances    ON song_dances(song_id);
        """)


# ── Biblioteker ───────────────────────────────────────────────────────────────

def add_library(path: str) -> int:
    with get_db() as conn:
        cur = conn.execute(
            "INSERT OR IGNORE INTO libraries (path) VALUES (?)", (path,)
        )
        if cur.lastrowid:
            return cur.lastrowid
        row = conn.execute("SELECT id FROM libraries WHERE path=?", (path,)).fetchone()
        return row["id"]


def get_libraries(active_only: bool = True) -> list[sqlite3.Row]:
    with get_db() as conn:
        if active_only:
            return conn.execute(
                "SELECT * FROM libraries WHERE is_active=1 ORDER BY path"
            ).fetchall()
        return conn.execute("SELECT * FROM libraries ORDER BY path").fetchall()


def remove_library(library_id: int):
    with get_db() as conn:
        conn.execute("UPDATE libraries SET is_active=0 WHERE id=?", (library_id,))


def update_library_scan_time(library_id: int):
    now = datetime.now(timezone.utc).isoformat()
    with get_db() as conn:
        conn.execute(
            "UPDATE libraries SET last_full_scan=? WHERE id=?", (now, library_id)
        )


# ── Sange ─────────────────────────────────────────────────────────────────────

def upsert_song(song_data: dict) -> str:
    """
    Indsæt eller opdater en sang baseret på local_path.
    Returnerer song_id.
    """
    import uuid
    with get_db() as conn:
        existing = conn.execute(
            "SELECT id FROM songs WHERE local_path=?", (song_data["local_path"],)
        ).fetchone()

        if existing:
            song_id = existing["id"]
            conn.execute("""
                UPDATE songs SET
                    title=?, artist=?, album=?, bpm=?, duration_sec=?,
                    file_format=?, file_modified_at=?, file_missing=0
                WHERE id=?
            """, (
                song_data.get("title", ""),
                song_data.get("artist", ""),
                song_data.get("album", ""),
                song_data.get("bpm", 0),
                song_data.get("duration_sec", 0),
                song_data.get("file_format", ""),
                song_data.get("file_modified_at", ""),
                song_id,
            ))
        else:
            song_id = str(uuid.uuid4())
            conn.execute("""
                INSERT INTO songs
                    (id, library_id, local_path, title, artist, album,
                     bpm, duration_sec, file_format, file_modified_at)
                VALUES (?,?,?,?,?,?,?,?,?,?)
            """, (
                song_id,
                song_data.get("library_id"),
                song_data["local_path"],
                song_data.get("title", ""),
                song_data.get("artist", ""),
                song_data.get("album", ""),
                song_data.get("bpm", 0),
                song_data.get("duration_sec", 0),
                song_data.get("file_format", ""),
                song_data.get("file_modified_at", ""),
            ))

        # Opdater danse hvis de er med i data
        if "dances" in song_data:
            conn.execute("DELETE FROM song_dances WHERE song_id=?", (song_id,))
            for i, dance_name in enumerate(song_data["dances"], start=1):
                conn.execute(
                    "INSERT INTO song_dances (song_id, dance_name, dance_order) VALUES (?,?,?)",
                    (song_id, dance_name, i),
                )

    return song_id


def mark_song_missing(local_path: str):
    with get_db() as conn:
        conn.execute(
            "UPDATE songs SET file_missing=1 WHERE local_path=?", (local_path,)
        )


def get_song_by_path(local_path: str) -> sqlite3.Row | None:
    with get_db() as conn:
        return conn.execute(
            "SELECT * FROM songs WHERE local_path=?", (local_path,)
        ).fetchone()


def search_songs(query: str, limit: int = 50) -> list[sqlite3.Row]:
    """Søg i titel, artist og dansenavne."""
    pattern = f"%{query}%"
    with get_db() as conn:
        return conn.execute("""
            SELECT DISTINCT s.* FROM songs s
            LEFT JOIN song_dances sd ON sd.song_id = s.id
            WHERE s.file_missing = 0
              AND (s.title LIKE ? OR s.artist LIKE ? OR s.album LIKE ? OR sd.dance_name LIKE ?)
            ORDER BY s.artist, s.title
            LIMIT ?
        """, (pattern, pattern, pattern, pattern, limit)).fetchall()


def get_songs_for_library(library_id: int) -> list[sqlite3.Row]:
    with get_db() as conn:
        return conn.execute(
            "SELECT * FROM songs WHERE library_id=? ORDER BY artist, title",
            (library_id,)
        ).fetchall()


def get_all_song_paths_for_library(library_id: int) -> dict[str, str]:
    """Returnerer {local_path: file_modified_at} — bruges til fuld scan."""
    with get_db() as conn:
        rows = conn.execute(
            "SELECT local_path, file_modified_at FROM songs WHERE library_id=?",
            (library_id,)
        ).fetchall()
        return {row["local_path"]: row["file_modified_at"] for row in rows}


# ── Afspilningslister ─────────────────────────────────────────────────────────

def create_playlist(name: str, description: str = "") -> int:
    with get_db() as conn:
        cur = conn.execute(
            "INSERT INTO playlists (name, description) VALUES (?,?)",
            (name, description)
        )
        return cur.lastrowid


def get_playlists() -> list[sqlite3.Row]:
    with get_db() as conn:
        return conn.execute(
            "SELECT * FROM playlists ORDER BY created_at DESC"
        ).fetchall()


def add_song_to_playlist(playlist_id: int, song_id: str, position: int | None = None) -> int:
    with get_db() as conn:
        if position is None:
            row = conn.execute(
                "SELECT MAX(position) as max_pos FROM playlist_songs WHERE playlist_id=?",
                (playlist_id,)
            ).fetchone()
            position = (row["max_pos"] or 0) + 1

        cur = conn.execute(
            "INSERT INTO playlist_songs (playlist_id, song_id, position) VALUES (?,?,?)",
            (playlist_id, song_id, position)
        )
        return cur.lastrowid


def update_playlist_song_status(playlist_song_id: int, status: str):
    valid = {"pending", "playing", "played", "skipped"}
    if status not in valid:
        raise ValueError(f"Ugyldig status: {status}")
    with get_db() as conn:
        conn.execute(
            "UPDATE playlist_songs SET status=? WHERE id=?",
            (status, playlist_song_id)
        )


def get_playlist_with_songs(playlist_id: int) -> dict:
    with get_db() as conn:
        playlist = conn.execute(
            "SELECT * FROM playlists WHERE id=?", (playlist_id,)
        ).fetchone()
        if not playlist:
            return {}

        songs = conn.execute("""
            SELECT ps.id as ps_id, ps.position, ps.status,
                   s.*, GROUP_CONCAT(sd.dance_name ORDER BY sd.dance_order) as dances
            FROM playlist_songs ps
            JOIN songs s ON s.id = ps.song_id
            LEFT JOIN song_dances sd ON sd.song_id = s.id
            WHERE ps.playlist_id = ?
            GROUP BY ps.id
            ORDER BY ps.position
        """, (playlist_id,)).fetchall()

        return {"playlist": dict(playlist), "songs": [dict(s) for s in songs]}
