"""Migration structure and consistency tests.

Verifies Alembic migrations have proper structure and Sprint 0 triggers.
"""

import ast
import importlib.util
from pathlib import Path

import pytest

MIGRATIONS_DIR = Path("src/noteflow/infrastructure/persistence/migrations/versions")


def _load_migration_module(path: Path) -> ast.Module:
    """Parse migration file as AST."""
    return ast.parse(path.read_text())


def _has_variable(path: Path, var_name: str) -> bool:
    """Check if migration file has a specific variable assignment."""
    tree = _load_migration_module(path)
    nodes = list(ast.walk(tree))
    assign_matches = [
        n for n in nodes
        if isinstance(n, ast.Assign)
        and any(isinstance(t, ast.Name) and t.id == var_name for t in n.targets)
    ]
    annassign_matches = [
        n for n in nodes
        if isinstance(n, ast.AnnAssign)
        and isinstance(n.target, ast.Name)
        and n.target.id == var_name
    ]
    return bool(assign_matches or annassign_matches)


def _has_function(path: Path, func_name: str) -> bool:
    """Check if migration file has a specific function definition."""
    tree = _load_migration_module(path)
    matches = [
        n for n in ast.walk(tree)
        if isinstance(n, ast.FunctionDef) and n.name == func_name
    ]
    return bool(matches)


def _find_all_migration_files() -> list[Path]:
    """Find all migration files in versions directory."""
    return sorted(MIGRATIONS_DIR.glob("*.py"))


class TestMigrationStructure:
    """Verify migration file structure."""

    def test_migrations_directory_exists(self) -> None:
        """Migrations directory should exist."""
        assert MIGRATIONS_DIR.exists(), f"Missing migrations directory: {MIGRATIONS_DIR}"

    def test_all_migrations_have_revision(self) -> None:
        """Each migration should have a revision identifier."""
        missing_revision = [
            path.name
            for path in _find_all_migration_files()
            if not _has_variable(path, "revision")
        ]
        assert not missing_revision, f"Missing 'revision' in: {missing_revision}"

    def test_all_migrations_have_down_revision(self) -> None:
        """Each migration should have a down_revision."""
        missing = [p.name for p in _find_all_migration_files() if not _has_variable(p, "down_revision")]
        assert not missing, f"Missing 'down_revision' in: {missing}"

    def test_all_migrations_have_upgrade_function(self) -> None:
        """Each migration should have an upgrade function."""
        missing = [p.name for p in _find_all_migration_files() if not _has_function(p, "upgrade")]
        assert not missing, f"Missing 'upgrade()' function in: {missing}"

    def test_all_migrations_have_downgrade_function(self) -> None:
        """Each migration should have a downgrade function."""
        missing = [p.name for p in _find_all_migration_files() if not _has_function(p, "downgrade")]
        assert not missing, f"Missing 'downgrade()' function in: {missing}"


class TestSprint0Triggers:
    """Verify Sprint 0 updated_at triggers exist."""

    @pytest.mark.parametrize(
        "migration_file",
        [
            pytest.param(
                "h2c3d4e5f6g7_add_named_entities_table.py",
                id="named-entities",
            ),
            pytest.param(
                "i3d4e5f6g7h8_add_webhook_tables.py",
                id="webhooks",
            ),
        ],
    )
    def test_migration_has_trigger_creation(self, migration_file: str) -> None:
        """Migration should create updated_at trigger in upgrade."""
        path = MIGRATIONS_DIR / migration_file
        assert path.exists(), f"Migration file not found: {migration_file}"

        content = path.read_text()
        assert "CREATE TRIGGER" in content, f"Missing CREATE TRIGGER in {migration_file}"
        assert "set_updated_at" in content, (
            f"Missing set_updated_at function reference in {migration_file}"
        )

    @pytest.mark.parametrize(
        "migration_file",
        [
            pytest.param(
                "h2c3d4e5f6g7_add_named_entities_table.py",
                id="named-entities",
            ),
            pytest.param(
                "i3d4e5f6g7h8_add_webhook_tables.py",
                id="webhooks",
            ),
        ],
    )
    def test_migration_has_trigger_drop_in_downgrade(self, migration_file: str) -> None:
        """Migration should drop trigger in downgrade."""
        path = MIGRATIONS_DIR / migration_file
        content = path.read_text()
        assert "DROP TRIGGER" in content, f"Missing DROP TRIGGER in {migration_file}"


class TestMigrationImports:
    """Verify migrations can be imported."""

    def test_can_import_alembic_config(self) -> None:
        """Alembic config should be importable."""
        spec = importlib.util.find_spec("noteflow.infrastructure.persistence.migrations")
        assert spec is not None, "migrations package not importable"

    def test_initial_schema_exists(self) -> None:
        """Initial schema migration should exist."""
        initial = MIGRATIONS_DIR / "6a9d9f408f40_initial_schema.py"
        assert initial.exists(), "Initial schema migration missing"
