"""PRAGMA user_version-based migration runner.

Each migration is a `.sql` file under `backend/src/cyclone/migrations/`
with a leading comment of the form `-- version: N`. The runner reads
the current `PRAGMA user_version`, sorts migrations by filename, and
applies any whose version is greater than the current value. Each
migration runs in its own transaction; a failure rolls back the
version bump so the next startup retries.

This is intentionally NOT Alembic. One operator, one machine, one
schema-version counter.

Convention: migrations are DDL-only. Do not use `--` comments except for the
required `-- version: N` header. Do not use string literals containing `;`.
This is enforced by the line-based comment stripper and naive `;` splitter.
"""

from __future__ import annotations

import re
from pathlib import Path

import sqlalchemy as sa

MIGRATIONS_DIR = Path(__file__).parent / "migrations"

_VERSION_RE = re.compile(r"^--\s*version:\s*(\d+)", re.MULTILINE)


def _migration_version(sql: str, filename: str) -> int:
    m = _VERSION_RE.search(sql)
    if not m:
        raise RuntimeError(
            f"{filename}: missing '-- version: N' header comment"
        )
    return int(m.group(1))


def _strip_comments(sql: str) -> str:
    """Remove `--` line comments; preserve everything else."""
    lines = []
    for line in sql.splitlines():
        stripped = line.strip()
        if stripped.startswith("--"):
            continue
        lines.append(line)
    return "\n".join(lines)


def _apply_migrations_until(engine: sa.Engine, target_version: int | None) -> None:
    """Apply migrations up to ``target_version`` (inclusive).

    If ``target_version`` is ``None``, apply every pending migration. If a
    migration's version is greater than ``target_version``, stop iterating.
    Idempotent: migrations whose version is <= the current ``PRAGMA user_version``
    are skipped.
    """
    with engine.begin() as conn:
        current = conn.exec_driver_sql("PRAGMA user_version").scalar() or 0

    for path in sorted(MIGRATIONS_DIR.glob("*.sql")):
        sql = path.read_text()
        version = _migration_version(sql, path.name)
        if version <= current:
            continue
        if target_version is not None and version > target_version:
            break

        statements_sql = _strip_comments(sql)
        statements = [s.strip() for s in statements_sql.split(";") if s.strip()]
        with engine.begin() as conn:
            for stmt in statements:
                conn.exec_driver_sql(stmt)
            conn.exec_driver_sql(f"PRAGMA user_version = {version}")
        current = version


def run(engine: sa.Engine) -> None:
    """Apply any pending migrations. Idempotent."""
    if not MIGRATIONS_DIR.exists():
        return  # no migrations directory yet — fresh checkout, no-op
    if not list(MIGRATIONS_DIR.glob("*.sql")):
        return
    _apply_migrations_until(engine, None)


def migrate_to_version(engine: sa.Engine, target_version: int) -> None:
    """Apply migrations up to and including ``target_version``. Idempotent.

    Reads the current ``PRAGMA user_version`` and applies any migrations
    whose version is in the range ``(current, target_version]``. Stops
    before applying migrations with version > ``target_version`` so the
    caller can seed data between N and N+1.

    Useful for tests that need to seed data after migrations ``0001..N``
    have applied but before ``N+1`` runs — exercise a migration against
    real, pre-existing rows rather than only verifying that post-migration
    inserts round-trip.

    Calling with ``target_version`` <= the current version is a no-op.
    """
    if not MIGRATIONS_DIR.exists():
        return
    _apply_migrations_until(engine, target_version)