"""SQLite connection helpers and forward-only migrations.

Connections use ``sqlite3`` with a few pragmas tuned for our workload:

* ``foreign_keys=ON`` — referential integrity for ``instructions`` →
  ``positions``.
* ``journal_mode=WAL`` — concurrent reader (the GUI) without blocking
  the engine writer.
* ``synchronous=NORMAL`` — durable enough for our use-case (we also
  append-fsync the audit chain) and noticeably faster than FULL.

Migrations live in ``state/migrations/NNNN_<name>.sql``. They are
applied in numeric order, each bumps ``PRAGMA user_version`` at the
last statement of the file. ``run_migrations`` is idempotent: it
re-checks ``user_version`` and replays only what is missing.
"""

from __future__ import annotations

import re
import sqlite3
from collections.abc import Iterator
from contextlib import contextmanager
from importlib import resources
from pathlib import Path

__all__ = ["connect", "current_version", "list_migrations", "run_migrations"]


_MIGRATION_PATTERN = re.compile(r"^(\d{4})_[a-z0-9_]+\.sql$")


def _apply_pragmas(conn: sqlite3.Connection) -> None:
    conn.execute("PRAGMA foreign_keys = ON")
    conn.execute("PRAGMA journal_mode = WAL")
    conn.execute("PRAGMA synchronous = NORMAL")


def connect(path: str | Path) -> sqlite3.Connection:
    """Open a connection to ``path`` with the standard pragmas applied."""
    db_path = Path(path)
    db_path.parent.mkdir(parents=True, exist_ok=True)
    conn = sqlite3.connect(
        str(db_path),
        isolation_level=None,  # autocommit; we manage transactions explicitly
        detect_types=sqlite3.PARSE_DECLTYPES,
    )
    conn.row_factory = sqlite3.Row
    _apply_pragmas(conn)
    return conn


@contextmanager
def transaction(conn: sqlite3.Connection) -> Iterator[sqlite3.Connection]:
    """Begin/commit/rollback wrapper that respects autocommit mode."""
    conn.execute("BEGIN")
    try:
        yield conn
    except Exception:
        conn.execute("ROLLBACK")
        raise
    else:
        conn.execute("COMMIT")


def current_version(conn: sqlite3.Connection) -> int:
    row = conn.execute("PRAGMA user_version").fetchone()
    return int(row[0])


def list_migrations() -> list[tuple[int, str, str]]:
    """Return ``[(version, filename, sql_text), ...]`` sorted by version."""
    pkg_files = resources.files("cerbero_bite.state.migrations")
    out: list[tuple[int, str, str]] = []
    for entry in pkg_files.iterdir():
        match = _MIGRATION_PATTERN.match(entry.name)
        if not match:
            continue
        version = int(match.group(1))
        out.append((version, entry.name, entry.read_text(encoding="utf-8")))
    out.sort(key=lambda triple: triple[0])
    return out


def run_migrations(conn: sqlite3.Connection) -> int:
    """Apply migrations whose version is greater than the current one.

    Returns the new ``user_version`` after the run.
    """
    applied_to = current_version(conn)
    for version, name, sql in list_migrations():
        if version <= applied_to:
            continue
        conn.executescript(sql)
        new_version = current_version(conn)
        if new_version != version:
            raise RuntimeError(
                f"Migration {name} did not bump user_version to {version} "
                f"(observed {new_version})"
            )
        applied_to = new_version
    return applied_to
