from __future__ import annotations

import os
from pathlib import Path
from typing import Mapping

from sqlalchemy import inspect, text

from services.shared.db import engine


ROOT = Path(__file__).resolve().parents[2]
MIGRATIONS_DIR = ROOT / "migrations" / "sql"
_MODE_ENV = "SCHEMA_MANAGEMENT_MODE"
_VALID_MODES = {"legacy", "migrations"}


def database_backend_name(db_engine=engine) -> str:
    return db_engine.url.get_backend_name()


def migration_suffix(db_engine=engine) -> str:
    return "postgres" if database_backend_name(db_engine) == "postgresql" else "sqlite"


def list_migration_files(*, db_engine=engine, migrations_dir: Path = MIGRATIONS_DIR) -> list[Path]:
    return sorted(migrations_dir.glob(f"*_{migration_suffix(db_engine)}.sql"))


def resolve_schema_management_mode(
    raw_mode: str | None,
    *,
    backend_name: str,
) -> str:
    normalized = str(raw_mode or "").strip().lower()
    if not normalized:
        normalized = "legacy" if backend_name == "sqlite" else "migrations"
    if normalized not in _VALID_MODES:
        allowed = ", ".join(sorted(_VALID_MODES))
        raise RuntimeError(f"{_MODE_ENV} must be one of: {allowed}")
    if backend_name != "sqlite" and normalized != "migrations":
        raise RuntimeError(
            f"{_MODE_ENV}=legacy is supported only for SQLite. "
            "Run `python scripts/migrate_core_db.py` and use SCHEMA_MANAGEMENT_MODE=migrations."
        )
    return normalized


def schema_management_mode(
    *,
    db_engine=engine,
    environ: Mapping[str, str] | None = None,
) -> str:
    source = environ or os.environ
    return resolve_schema_management_mode(
        source.get(_MODE_ENV),
        backend_name=database_backend_name(db_engine),
    )


def ensure_schema_migrations_table(*, db_engine=engine) -> None:
    with db_engine.begin() as conn:
        conn.execute(
            text(
                """
                CREATE TABLE IF NOT EXISTS schema_migrations (
                  version TEXT PRIMARY KEY,
                  applied_at TEXT NOT NULL
                )
                """
            )
        )


def has_schema_migrations_table(*, db_engine=engine) -> bool:
    return inspect(db_engine).has_table("schema_migrations")


def applied_migration_versions(*, db_engine=engine) -> set[str]:
    if not has_schema_migrations_table(db_engine=db_engine):
        return set()
    with db_engine.begin() as conn:
        rows = conn.execute(text("SELECT version FROM schema_migrations")).fetchall()
        return {str(row[0]) for row in rows}


def missing_migration_versions(*, db_engine=engine, migrations_dir: Path = MIGRATIONS_DIR) -> list[str]:
    expected = [path.name for path in list_migration_files(db_engine=db_engine, migrations_dir=migrations_dir)]
    applied = applied_migration_versions(db_engine=db_engine)
    return [name for name in expected if name not in applied]


def validate_schema_migrations_applied(*, db_engine=engine, migrations_dir: Path = MIGRATIONS_DIR) -> None:
    expected = list_migration_files(db_engine=db_engine, migrations_dir=migrations_dir)
    if not expected:
        raise RuntimeError(f"No migration files found for backend {database_backend_name(db_engine)} in {migrations_dir}")

    if not has_schema_migrations_table(db_engine=db_engine):
        raise RuntimeError(
            "Database schema is not initialized via migrations. "
            "Run `python scripts/migrate_core_db.py` before starting services."
        )

    missing = missing_migration_versions(db_engine=db_engine, migrations_dir=migrations_dir)
    if not missing:
        return

    preview = ", ".join(missing[:5])
    remainder = "" if len(missing) <= 5 else f" ... (+{len(missing) - 5} more)"
    raise RuntimeError(
        "Database schema is behind the checked-in migrations. "
        f"Missing: {preview}{remainder}. "
        "Run `python scripts/migrate_core_db.py` before starting services."
    )
