"""
Sequential migration runner for Padelnomics.

Manages SQLite schema evolution by replaying migrations in order.
All databases — fresh and existing — go through the same path.

Algorithm
---------
1. Connect to the SQLite database (create file if missing).
2. Set WAL journal mode and enable foreign keys.
3. Create the _migrations tracking table if it doesn't exist.
4. Discover version files in versions/ matching NNNN_*.py.
5. Diff discovered versions against the _migrations tracking table.
6. Run pending migrations in order, record each in _migrations.
7. Commit the transaction and print a summary with table names.

Adding a new migration
----------------------
1. Create ``versions/NNNN_description.py`` with a single ``up(conn)``
   function that receives an *uncommitted* ``sqlite3.Connection``.
   The runner commits after all pending migrations succeed (batch
   atomicity), so do NOT call ``conn.commit()`` inside ``up()``.
2. Use IF NOT EXISTS / IF EXISTS guards for idempotency.

Design decisions
----------------
- **Single code path**: Fresh and existing databases both replay
  migrations. 0000_initial_schema.py is the baseline.
- **Sync sqlite3, not aiosqlite**: Migrations run at startup *before*
  the async event loop, so we use the stdlib sqlite3 module directly.
- **up(conn) receives an uncommitted connection**: All pending migrations
  share a single transaction. If any migration fails, the entire batch
  rolls back, leaving the DB in its previous consistent state.
"""

import importlib
import logging
import os
import re
import sqlite3
import sys
from pathlib import Path

logger = logging.getLogger(__name__)

sys.path.insert(0, str(Path(__file__).parent.parent.parent.parent))

from dotenv import load_dotenv  # noqa: E402

load_dotenv()

VERSIONS_DIR = Path(__file__).parent / "versions"
VERSION_RE = re.compile(r"^(\d{4})_.+\.py$")


def _discover_versions():
    """Return sorted list of (name, module_path) for version files."""
    if not VERSIONS_DIR.is_dir():
        return []
    versions = []
    for f in sorted(VERSIONS_DIR.iterdir()):
        if VERSION_RE.match(f.name):
            versions.append(f.stem)  # e.g. "0001_rename_ls_to_paddle"
    return versions


def migrate(db_path=None):
    if db_path is None:
        db_path = os.getenv("DATABASE_PATH", "data/app.db")
    Path(db_path).parent.mkdir(parents=True, exist_ok=True)

    conn = sqlite3.connect(db_path)
    conn.execute("PRAGMA journal_mode=WAL")
    conn.execute("PRAGMA foreign_keys=ON")

    # Ensure tracking table exists before anything else
    conn.execute("""
        CREATE TABLE IF NOT EXISTS _migrations (
            id INTEGER PRIMARY KEY AUTOINCREMENT,
            name TEXT UNIQUE NOT NULL,
            applied_at TEXT NOT NULL DEFAULT (datetime('now'))
        )
    """)
    conn.commit()

    versions = _discover_versions()
    applied = {
        row[0]
        for row in conn.execute("SELECT name FROM _migrations").fetchall()
    }
    pending = [v for v in versions if v not in applied]

    if pending:
        for name in pending:
            logger.info("Applying %s...", name)
            mod = importlib.import_module(
                f"padelnomics.migrations.versions.{name}"
            )
            mod.up(conn)
            conn.execute(
                "INSERT INTO _migrations (name) VALUES (?)", (name,)
            )
        conn.commit()
        logger.info("Applied %s migration(s): %s", len(pending), db_path)
    else:
        logger.info("All migrations already applied: %s", db_path)

    # Show tables (excluding internal sqlite/fts tables)
    cursor = conn.execute(
        "SELECT name FROM sqlite_master WHERE type='table'"
        " AND name NOT LIKE 'sqlite_%'"
        " ORDER BY name"
    )
    tables = [row[0] for row in cursor.fetchall()]
    logger.info("Tables: %s", ", ".join(tables))

    conn.close()


if __name__ == "__main__":
    logging.basicConfig(level=logging.INFO, format="%(levelname)-8s %(message)s")
    migrate()
