"""Schema migration system for safe database upgrades."""

import sqlite3
from typing import Callable

from memotrail.utils import get_logger

logger = get_logger("memotrail.storage.migrations")


# Migration registry: version -> (description, sql_or_callable)
MIGRATIONS: list[tuple[int, str, str | Callable]] = [
    (1, "Base schema (sessions, messages, decisions, memories, indexed_files)",
     "-- Base schema created by SQLiteStore.__init__"),

    (2, "Add entities and relations tables",
     """
     CREATE TABLE IF NOT EXISTS entities (
         id TEXT PRIMARY KEY,
         name TEXT NOT NULL,
         entity_type TEXT NOT NULL,
         description TEXT,
         first_seen_session TEXT REFERENCES sessions(id),
         last_seen_session TEXT REFERENCES sessions(id),
         mention_count INTEGER DEFAULT 1,
         created_at TEXT NOT NULL,
         updated_at TEXT NOT NULL
     );
     CREATE TABLE IF NOT EXISTS relations (
         id TEXT PRIMARY KEY,
         source_entity_id TEXT NOT NULL REFERENCES entities(id),
         target_entity_id TEXT NOT NULL REFERENCES entities(id),
         relation_type TEXT NOT NULL,
         context TEXT,
         session_id TEXT REFERENCES sessions(id),
         confidence REAL DEFAULT 1.0,
         created_at TEXT NOT NULL
     );
     CREATE INDEX IF NOT EXISTS idx_entities_name ON entities(name);
     CREATE INDEX IF NOT EXISTS idx_entities_type ON entities(entity_type);
     CREATE INDEX IF NOT EXISTS idx_relations_source ON relations(source_entity_id);
     CREATE INDEX IF NOT EXISTS idx_relations_target ON relations(target_entity_id);
     """),

    (3, "Add fact_history table for temporal tracking",
     """
     CREATE TABLE IF NOT EXISTS fact_history (
         id TEXT PRIMARY KEY,
         fact_type TEXT NOT NULL,
         fact_id TEXT NOT NULL,
         previous_value TEXT,
         new_value TEXT NOT NULL,
         change_type TEXT NOT NULL,
         session_id TEXT REFERENCES sessions(id),
         changed_at TEXT NOT NULL
     );
     CREATE INDEX IF NOT EXISTS idx_fact_history_fact ON fact_history(fact_id);
     CREATE INDEX IF NOT EXISTS idx_fact_history_time ON fact_history(changed_at);
     """),

    (4, "Add scope and project columns to memories",
     """
     -- SQLite doesn't support ADD COLUMN IF NOT EXISTS, so we catch errors
     ALTER TABLE memories ADD COLUMN scope TEXT DEFAULT 'user';
     """),
]


class Migrator:
    """Handles versioned schema migrations for SQLite."""

    def __init__(self, conn: sqlite3.Connection):
        self.conn = conn
        self._ensure_migration_table()

    def _ensure_migration_table(self) -> None:
        """Create the schema_version table if needed."""
        self.conn.execute("""
            CREATE TABLE IF NOT EXISTS schema_version (
                version INTEGER PRIMARY KEY,
                description TEXT,
                applied_at TEXT NOT NULL
            )
        """)
        self.conn.commit()

    def get_current_version(self) -> int:
        """Get the current schema version."""
        row = self.conn.execute(
            "SELECT MAX(version) as v FROM schema_version"
        ).fetchone()
        return row[0] if row and row[0] else 0

    def get_target_version(self) -> int:
        """Get the latest available migration version."""
        return MIGRATIONS[-1][0] if MIGRATIONS else 0

    def migrate(self) -> list[int]:
        """Run all pending migrations.

        Returns:
            List of applied migration versions
        """
        current = self.get_current_version()
        applied = []

        for version, description, migration in MIGRATIONS:
            if version <= current:
                continue

            logger.info(f"Applying migration v{version}: {description}")

            try:
                if isinstance(migration, str):
                    if not migration.strip().startswith("--"):
                        self.conn.executescript(migration)
                elif callable(migration):
                    migration(self.conn)

                from datetime import datetime, timezone
                self.conn.execute(
                    "INSERT INTO schema_version (version, description, applied_at) "
                    "VALUES (?, ?, ?)",
                    (version, description, datetime.now(timezone.utc).isoformat()),
                )
                self.conn.commit()
                applied.append(version)
                logger.info(f"Migration v{version} applied successfully")
            except sqlite3.OperationalError as e:
                # Handle "column already exists" gracefully
                if "duplicate column" in str(e).lower() or "already exists" in str(e).lower():
                    logger.warning(f"Migration v{version} skipped (already applied): {e}")
                    from datetime import datetime, timezone
                    self.conn.execute(
                        "INSERT OR IGNORE INTO schema_version (version, description, applied_at) "
                        "VALUES (?, ?, ?)",
                        (version, description, datetime.now(timezone.utc).isoformat()),
                    )
                    self.conn.commit()
                    applied.append(version)
                else:
                    logger.error(f"Migration v{version} failed: {e}")
                    raise

        return applied

    def get_applied_migrations(self) -> list[dict]:
        """Get list of applied migrations."""
        rows = self.conn.execute(
            "SELECT * FROM schema_version ORDER BY version"
        ).fetchall()
        return [{"version": r[0], "description": r[1], "applied_at": r[2]} for r in rows]
