"""Tests for migration registry and discovery."""

from __future__ import annotations

import pytest

from repo_core.migrations.registry import get_postgres_migrations, get_doris_migrations
from repo_core.migrations.runner import Migration
from repo_core.migrations.builder import RawSqlOp, AlterTableOp, CreateTableOp


# ===========================================================================
# Registry
# ===========================================================================


def test_get_postgres_migrations_returns_list():
    migrations = get_postgres_migrations()
    assert isinstance(migrations, list)
    assert len(migrations) > 0
    assert all(isinstance(m, Migration) for m in migrations)


def test_get_doris_migrations_returns_list():
    migrations = get_doris_migrations()
    assert isinstance(migrations, list)
    assert len(migrations) > 0
    assert all(isinstance(m, Migration) for m in migrations)


def test_migrations_have_required_fields():
    for m in get_postgres_migrations():
        assert m.version, f"Migration missing version"
        assert m.description, f"Migration {m.version} missing description"
        assert m.up is not None, f"Migration {m.version} missing up op"
        # RawSqlOp migrations may have down=None (e.g., CREATE EXTENSION is never dropped)
        if isinstance(m.up, RawSqlOp):
            continue
        assert m.down is not None, f"Migration {m.version} missing down op"
        # up op produces SQL
        sql = m.up.sql()
        assert len(sql) > 0, f"Migration {m.version} up produces no SQL"


def test_versions_are_unique():
    versions = [m.version for m in get_postgres_migrations()]
    assert len(versions) == len(set(versions)), f"Duplicate versions: {versions}"


def test_discovery_sorts_by_filename():
    """Migrations are discovered in filename order, which is the execution order."""
    from repo_core.migrations.discovery import discover_migrations
    pg = discover_migrations("postgresql")
    # The first migration should be from 0001 file (initial setup)
    assert len(pg) > 0


def test_up_to_file_filter():
    from repo_core.migrations.discovery import discover_migrations
    all_migs = discover_migrations("postgresql")
    subset = discover_migrations("postgresql", up_to_file="0001")
    # up_to_file=0001 should include at least the first file's migrations
    assert len(subset) <= len(all_migs)
    assert len(subset) > 0


def test_doris_migrations_have_dialect():
    for m in get_doris_migrations():
        assert m.dialect == "doris", f"Migration {m.version} should be doris dialect"


def test_every_migration_has_table_def():
    """Every migration up Op must produce valid SQL, and table-creating Ops
    must reference a table and at least one column."""
    from repo_core.migrations.builder import CreateTableOp
    for mig in get_postgres_migrations():
        sql = mig.up.sql()
        assert sql, f"Migration {mig.version} produces no SQL"
        assert all(isinstance(s, str) for s in sql), f"Non-string SQL in {mig.version}"
        # Table-creating ops must define columns
        if isinstance(mig.up, CreateTableOp):
            assert any("CREATE TABLE" in s for s in sql), f"No CREATE TABLE in {mig.version}: {sql}"
            assert any("COLUMN" not in s.upper() or "(" in s for s in sql), f"CreateTableOp {mig.version} has no columns"


# ===========================================================================
# Bloom filter index
# ===========================================================================


def test_bloom_extension_migration_exists():
    """The 0011 migration file must include a CREATE EXTENSION bloom step."""
    migrations = get_postgres_migrations()
    ext_mig = next((m for m in migrations if "bloom" in m.description.lower() and "extension" in m.description.lower()), None)
    assert ext_mig is not None, "No bloom extension migration found"
    assert isinstance(ext_mig.up, RawSqlOp)
    assert "CREATE EXTENSION" in ext_mig.up.sql()[0]
    assert "bloom" in ext_mig.up.sql()[0]
    assert ext_mig.down is None, "Extension migration should not have a down (don't drop extensions)"


def test_bloom_index_migration_exists():
    """The 0011 migration file must include a bloom index on url."""
    migrations = get_postgres_migrations()
    bloom_mig = next(
        (m for m in migrations if "bloom" in m.description.lower() and "url" in m.description.lower()),
        None,
    )
    assert bloom_mig is not None, "No bloom index migration on url found"
    assert isinstance(bloom_mig.up, AlterTableOp)
    sql = bloom_mig.up.sql()
    assert any("USING bloom" in s for s in sql), f"Bloom USING clause not found in: {sql}"
    assert any("url" in s for s in sql), f"url column not found in: {sql}"
    assert any("WITH (length = 80)" in s for s in sql), f"WITH clause not found in: {sql}"


def test_raw_sql_op_single_statement():
    """RawSqlOp with a single string."""
    op = RawSqlOp("CREATE EXTENSION IF NOT EXISTS bloom")
    sql = op.sql()
    assert sql == ["CREATE EXTENSION IF NOT EXISTS bloom"]


def test_raw_sql_op_multiple_statements():
    """RawSqlOp with a list of statements."""
    op = RawSqlOp(["CREATE EXTENSION IF NOT EXISTS bloom", "CREATE EXTENSION IF NOT EXISTS pg_trgm"])
    sql = op.sql()
    assert len(sql) == 2
    assert sql[0] == "CREATE EXTENSION IF NOT EXISTS bloom"
    assert sql[1] == "CREATE EXTENSION IF NOT EXISTS pg_trgm"


def test_raw_sql_op_empty_list():
    """RawSqlOp with empty list produces empty SQL."""
    op = RawSqlOp([])
    assert op.sql() == []


def test_migration_builder_index_bloom_create_table():
    """MigrationBuilder.index() with bloom using in create_table context."""
    from repo_core.migrations.builder import MigrationBuilder
    op = (
        MigrationBuilder("test_table")
        .uuid("id", primary_key=True, nullable=False)
        .text("url", nullable=True)
        .index(["url"], using="bloom", with_params={"length": 80})
        .create_table()
    )
    sql = op.sql()
    assert any("USING bloom" in s for s in sql), f"Missing bloom in: {sql}"
    assert any("WITH (length = 80)" in s for s in sql), f"Missing WITH in: {sql}"


def test_migration_builder_add_index_bloom_alter_table():
    """MigrationBuilder.add_index() with bloom using in alter_table context."""
    from repo_core.migrations.builder import MigrationBuilder
    op = (
        MigrationBuilder("test_table")
        .add_index(["url"], using="bloom", with_params={"length": 80})
        .alter_table()
    )
    sql = op.sql()
    assert any("USING bloom" in s for s in sql), f"Missing bloom in: {sql}"
    assert any("WITH (length = 80)" in s for s in sql), f"Missing WITH in: {sql}"


def test_migration_builder_index_bloom_no_with_params():
    """Bloom index without with_params still works (no WITH clause)."""
    from repo_core.migrations.builder import MigrationBuilder
    op = (
        MigrationBuilder("test_table")
        .add_index(["url"], using="bloom")
        .alter_table()
    )
    sql = op.sql()
    assert any("USING bloom" in s for s in sql)
    assert not any("WITH" in s for s in sql), f"WITH should not be present, got: {sql}"
