"""Tests for the schema migration runner."""
import sqlite3

import pytest

from greymatter_core.db import SQLiteDatabase
from greymatter_core.schema.migrations import (
    _MIGRATIONS,
    _ensure_metadata_table,
    apply_all,
    get_current_version,
    get_pending,
    register,
)


@pytest.fixture(autouse=True)
def clean_migrations():
    """Reset migration registry between tests."""
    saved = dict(_MIGRATIONS)
    _MIGRATIONS.clear()
    yield
    _MIGRATIONS.clear()
    _MIGRATIONS.update(saved)


@pytest.fixture
def db():
    """In-memory SQLite database with metadata table."""
    d = SQLiteDatabase(":memory:")
    _ensure_metadata_table(d)
    return d


class TestMigrationRegistry:
    """Tests for migration registration and discovery."""

    def test_register_adds_migration(self):
        @register(1, "Initial schema")
        def migrate_001(db):
            pass

        assert 1 in _MIGRATIONS
        assert _MIGRATIONS[1][0] == "Initial schema"

    def test_get_current_version_default(self, db):
        version = get_current_version(db)
        assert version == 0

    def test_get_current_version_after_set(self, db):
        db.execute(
            "INSERT INTO _schema_metadata (key, value) VALUES (?, ?)",
            ("schema_version", "3"),
        )
        assert get_current_version(db) == 3

    def test_get_pending_returns_unapplied(self, db):
        @register(1, "First")
        def m1(db):
            pass

        @register(2, "Second")
        def m2(db):
            pass

        pending = get_pending(db)
        assert len(pending) == 2
        assert pending[0][0] == 1
        assert pending[1][0] == 2

    def test_get_pending_skips_applied(self, db):
        @register(1, "First")
        def m1(db):
            pass

        @register(2, "Second")
        def m2(db):
            pass

        db.execute(
            "INSERT INTO _schema_metadata (key, value) VALUES (?, ?)",
            ("schema_version", "1"),
        )
        pending = get_pending(db)
        assert len(pending) == 1
        assert pending[0][0] == 2


class TestMigrationApplication:
    """Tests for applying migrations."""

    def test_apply_all_runs_in_order(self, db):
        calls = []

        @register(1, "Create foo")
        def m1(db):
            calls.append(1)
            db.execute("CREATE TABLE IF NOT EXISTS foo (id TEXT PRIMARY KEY)")

        @register(2, "Create bar")
        def m2(db):
            calls.append(2)
            db.execute("CREATE TABLE IF NOT EXISTS bar (id TEXT PRIMARY KEY)")

        applied = apply_all(db)
        assert calls == [1, 2]
        assert len(applied) == 2
        assert applied[0] == (1, "Create foo")
        assert applied[1] == (2, "Create bar")

    def test_apply_all_updates_version(self, db):
        @register(1, "First")
        def m1(db):
            pass

        @register(2, "Second")
        def m2(db):
            pass

        apply_all(db)
        assert get_current_version(db) == 2

    def test_apply_all_idempotent(self, db):
        calls = []

        @register(1, "Once")
        def m1(db):
            calls.append(1)

        apply_all(db)
        apply_all(db)  # Second call should be no-op
        assert calls == [1]

    def test_apply_all_empty_when_no_migrations(self, db):
        applied = apply_all(db)
        assert applied == []

    def test_out_of_order_registration_applied_in_order(self, db):
        """Migrations registered out of version order still apply in order."""
        order = []

        @register(3, "Third")
        def m3(db):
            order.append(3)

        @register(1, "First")
        def m1(db):
            order.append(1)

        @register(2, "Second")
        def m2(db):
            order.append(2)

        apply_all(db)
        assert order == [1, 2, 3]
        assert get_current_version(db) == 3

    def test_incremental_apply(self, db):
        """New migrations added after first apply are picked up."""
        @register(1, "Initial")
        def m1(db):
            db.execute("CREATE TABLE IF NOT EXISTS _inc1 (id TEXT PRIMARY KEY)")

        apply_all(db)
        assert get_current_version(db) == 1

        @register(2, "Incremental")
        def m2(db):
            db.execute("CREATE TABLE IF NOT EXISTS _inc2 (id TEXT PRIMARY KEY)")

        applied = apply_all(db)
        assert len(applied) == 1
        assert applied[0] == (2, "Incremental")
        assert get_current_version(db) == 2


class TestMetadataTable:
    def test_ensure_creates_table(self):
        db = SQLiteDatabase(":memory:")
        _ensure_metadata_table(db)
        row = db.execute_one(
            "SELECT name FROM sqlite_master WHERE type='table' AND name='_schema_metadata'"
        )
        assert row is not None

    def test_ensure_is_idempotent(self):
        db = SQLiteDatabase(":memory:")
        _ensure_metadata_table(db)
        _ensure_metadata_table(db)
        assert get_current_version(db) == 0
