from __future__ import annotations

import sqlite3
from pathlib import Path

from muleline.config import DB_PATH, DATA_DIR

SCHEMA_VERSION = 8

MIGRATIONS = [
    # v1: Core sync tables
    """
    CREATE TABLE IF NOT EXISTS pending_uploads (
        id TEXT PRIMARY KEY,
        owner_id TEXT NOT NULL,
        original_name TEXT NOT NULL,
        mime_type TEXT DEFAULT '',
        total_size INTEGER NOT NULL,
        total_chunks INTEGER NOT NULL,
        received_chunks INTEGER DEFAULT 0,
        file_hash TEXT DEFAULT '',
        metadata TEXT DEFAULT '{}',
        status TEXT DEFAULT 'uploading',
        created_at REAL NOT NULL,
        updated_at REAL NOT NULL,
        expires_at REAL NOT NULL
    );

    CREATE TABLE IF NOT EXISTS files (
        id INTEGER PRIMARY KEY AUTOINCREMENT,
        owner_id TEXT NOT NULL,
        filename TEXT NOT NULL,
        original_name TEXT NOT NULL,
        mime_type TEXT DEFAULT '',
        file_size INTEGER NOT NULL,
        file_hash TEXT NOT NULL,
        storage_backend TEXT DEFAULT 'local',
        storage_key TEXT DEFAULT '',
        metadata TEXT DEFAULT '{}',
        created_at REAL NOT NULL,
        updated_at REAL NOT NULL
    );
    CREATE INDEX IF NOT EXISTS idx_files_hash ON files(file_hash, owner_id);
    CREATE INDEX IF NOT EXISTS idx_files_owner ON files(owner_id);

    CREATE TABLE IF NOT EXISTS devices (
        id INTEGER PRIMARY KEY AUTOINCREMENT,
        owner_id TEXT NOT NULL,
        device_id TEXT NOT NULL UNIQUE,
        device_name TEXT DEFAULT '',
        platform TEXT DEFAULT '',
        last_sync_at REAL,
        created_at REAL NOT NULL,
        updated_at REAL NOT NULL
    );

    CREATE TABLE IF NOT EXISTS sync_log (
        id INTEGER PRIMARY KEY AUTOINCREMENT,
        device_id TEXT NOT NULL,
        owner_id TEXT NOT NULL,
        action TEXT NOT NULL,
        file_id INTEGER,
        file_hash TEXT DEFAULT '',
        bytes_transferred INTEGER DEFAULT 0,
        status TEXT DEFAULT 'ok',
        error_detail TEXT DEFAULT '',
        created_at REAL NOT NULL
    );

    CREATE TABLE IF NOT EXISTS schema_version (
        version INTEGER PRIMARY KEY
    );
    INSERT OR IGNORE INTO schema_version (version) VALUES (1);
    """,

    # v2: Albums and public sharing
    """
    CREATE TABLE IF NOT EXISTS albums (
        id INTEGER PRIMARY KEY AUTOINCREMENT,
        owner_id TEXT NOT NULL,
        name TEXT NOT NULL,
        description TEXT DEFAULT '',
        cover_file_id INTEGER,
        created_at REAL NOT NULL,
        updated_at REAL NOT NULL
    );

    CREATE TABLE IF NOT EXISTS album_files (
        album_id INTEGER NOT NULL,
        file_id INTEGER NOT NULL,
        added_at REAL NOT NULL,
        PRIMARY KEY (album_id, file_id)
    );

    CREATE TABLE IF NOT EXISTS shared_links (
        id TEXT PRIMARY KEY,
        owner_id TEXT NOT NULL,
        link_type TEXT NOT NULL,
        target_id INTEGER NOT NULL,
        password_hash TEXT DEFAULT '',
        expires_at REAL,
        allow_download INTEGER DEFAULT 1,
        view_count INTEGER DEFAULT 0,
        created_at REAL NOT NULL
    );
    INSERT OR IGNORE INTO schema_version (version) VALUES (2);
    """,

    # v3: EXIF columns on files for fast querying
    """
    ALTER TABLE files ADD COLUMN date_taken REAL;
    ALTER TABLE files ADD COLUMN gps_lat REAL;
    ALTER TABLE files ADD COLUMN gps_lon REAL;
    ALTER TABLE files ADD COLUMN width INTEGER;
    ALTER TABLE files ADD COLUMN height INTEGER;
    CREATE INDEX IF NOT EXISTS idx_files_date ON files(owner_id, date_taken);
    INSERT OR IGNORE INTO schema_version (version) VALUES (3);
    """,

    # v4: Face recognition tables
    """
    CREATE TABLE IF NOT EXISTS known_people (
        id INTEGER PRIMARY KEY AUTOINCREMENT,
        owner_id TEXT NOT NULL,
        name TEXT NOT NULL,
        reference_item_id INTEGER,
        created_at REAL NOT NULL
    );
    CREATE TABLE IF NOT EXISTS item_people (
        id INTEGER PRIMARY KEY AUTOINCREMENT,
        item_id INTEGER NOT NULL REFERENCES files(id) ON DELETE CASCADE,
        person_id INTEGER NOT NULL REFERENCES known_people(id),
        source TEXT DEFAULT 'face_recognition',
        created_at REAL NOT NULL
    );
    CREATE TABLE IF NOT EXISTS face_embeddings (
        id INTEGER PRIMARY KEY AUTOINCREMENT,
        item_id INTEGER NOT NULL REFERENCES files(id) ON DELETE CASCADE,
        person_id INTEGER REFERENCES known_people(id),
        bbox_x INTEGER NOT NULL,
        bbox_y INTEGER NOT NULL,
        bbox_w INTEGER NOT NULL,
        bbox_h INTEGER NOT NULL,
        embedding BLOB NOT NULL,
        confidence REAL DEFAULT 1.0,
        crop_filename TEXT DEFAULT '',
        cluster_id INTEGER,
        source TEXT DEFAULT 'auto',
        created_at REAL NOT NULL
    );
    CREATE INDEX IF NOT EXISTS idx_face_item ON face_embeddings(item_id);
    CREATE INDEX IF NOT EXISTS idx_face_person ON face_embeddings(person_id);
    CREATE INDEX IF NOT EXISTS idx_face_cluster ON face_embeddings(cluster_id);
    CREATE INDEX IF NOT EXISTS idx_known_people_owner ON known_people(owner_id);
    CREATE INDEX IF NOT EXISTS idx_item_people_item ON item_people(item_id);
    CREATE INDEX IF NOT EXISTS idx_item_people_person ON item_people(person_id);
    INSERT OR IGNORE INTO schema_version (version) VALUES (4);
    """,

    # v5: User authentication
    """
    CREATE TABLE IF NOT EXISTS users (
        id INTEGER PRIMARY KEY AUTOINCREMENT,
        username TEXT NOT NULL UNIQUE,
        password_hash TEXT NOT NULL,
        password_salt TEXT NOT NULL,
        tier TEXT DEFAULT 'free',
        stripe_customer_id TEXT,
        created_at REAL NOT NULL
    );
    CREATE INDEX IF NOT EXISTS idx_users_username ON users(username);
    INSERT OR IGNORE INTO schema_version (version) VALUES (5);
    """,

    # v6: CLIP semantic search embeddings
    """
    CREATE TABLE IF NOT EXISTS clip_embeddings (
        id INTEGER PRIMARY KEY AUTOINCREMENT,
        item_id INTEGER NOT NULL UNIQUE REFERENCES files(id) ON DELETE CASCADE,
        embedding BLOB NOT NULL,
        model_name TEXT DEFAULT 'ViT-B-32',
        created_at REAL NOT NULL
    );
    CREATE INDEX IF NOT EXISTS idx_clip_item ON clip_embeddings(item_id);
    INSERT OR IGNORE INTO schema_version (version) VALUES (6);
    """,

    # v7: Soft delete + favorites
    """
    ALTER TABLE files ADD COLUMN deleted_at REAL;
    CREATE INDEX IF NOT EXISTS idx_files_deleted ON files(owner_id, deleted_at);

    CREATE TABLE IF NOT EXISTS favorites (
        user_id TEXT NOT NULL,
        file_id INTEGER NOT NULL REFERENCES files(id) ON DELETE CASCADE,
        created_at REAL NOT NULL,
        PRIMARY KEY (user_id, file_id)
    );
    CREATE INDEX IF NOT EXISTS idx_favorites_user ON favorites(user_id);

    INSERT OR IGNORE INTO schema_version (version) VALUES (7);
    """,

    # v8: Receipts & invoices
    """
    CREATE TABLE IF NOT EXISTS receipts (
        id INTEGER PRIMARY KEY AUTOINCREMENT,
        owner_id TEXT NOT NULL,
        vendor TEXT NOT NULL,
        amount_cents INTEGER NOT NULL DEFAULT 0,
        currency TEXT DEFAULT 'USD',
        receipt_date TEXT NOT NULL,
        category TEXT DEFAULT '',
        description TEXT DEFAULT '',
        source TEXT DEFAULT 'manual',
        source_id TEXT DEFAULT '',
        attachment_file_id INTEGER REFERENCES files(id),
        dedup_hash TEXT UNIQUE,
        raw_text TEXT DEFAULT '',
        created_at REAL NOT NULL
    );
    CREATE INDEX IF NOT EXISTS idx_receipts_owner ON receipts(owner_id);
    CREATE INDEX IF NOT EXISTS idx_receipts_date ON receipts(owner_id, receipt_date);
    CREATE INDEX IF NOT EXISTS idx_receipts_vendor ON receipts(owner_id, vendor);
    CREATE INDEX IF NOT EXISTS idx_receipts_dedup ON receipts(dedup_hash);
    INSERT OR IGNORE INTO schema_version (version) VALUES (8);
    """,
]


def get_db() -> sqlite3.Connection:
    DATA_DIR.mkdir(parents=True, exist_ok=True)
    conn = sqlite3.connect(str(DB_PATH), timeout=10)
    conn.row_factory = sqlite3.Row
    conn.execute("PRAGMA journal_mode=WAL")
    conn.execute("PRAGMA foreign_keys=ON")
    return conn


def run_migrations(conn: sqlite3.Connection | None = None):
    close = False
    if conn is None:
        conn = get_db()
        close = True

    conn.execute(
        "CREATE TABLE IF NOT EXISTS schema_version (version INTEGER PRIMARY KEY)"
    )
    row = conn.execute("SELECT MAX(version) FROM schema_version").fetchone()
    current = row[0] if row[0] is not None else 0

    for i, sql in enumerate(MIGRATIONS, start=1):
        if i > current:
            conn.executescript(sql)

    conn.commit()
    if close:
        conn.close()
