"""
Tests for Alembic database migrations.

Verifies that migrations can be applied and rolled back correctly.
These tests use the actual database and should be run with caution
on shared environments.

What: Tests for database migration integrity
Why: Ensures migrations can be applied cleanly on fresh databases
     and rolled back safely for disaster recovery
"""
import pytest
from alembic import command
from alembic.config import Config
from sqlalchemy import create_engine, inspect, text

from app.config import get_settings


@pytest.fixture
def alembic_config():
    """Create Alembic config pointing to the backend directory."""
    config = Config("/mnt/NV2/Development/strat-gameplay-webapp/backend/alembic.ini")
    return config


@pytest.fixture
def sync_engine():
    """Create synchronous engine for migration testing."""
    settings = get_settings()
    sync_url = settings.database_url.replace("+asyncpg", "+psycopg2")
    return create_engine(sync_url)


class TestMigrationHistory:
    """Tests for migration chain integrity."""

    def test_migration_history_valid(self, alembic_config):
        """
        Verify migration chain is valid and has no gaps.

        What: Checks that all migrations reference existing predecessors
        Why: Broken migration chains cause 'revision not found' errors
        """
        from alembic.script import ScriptDirectory

        script = ScriptDirectory.from_config(alembic_config)
        revisions = list(script.walk_revisions())

        # Should have at least 2 migrations (001 and 004)
        assert len(revisions) >= 2, "Expected at least 2 migrations"

        # All revisions except base should have a down_revision that exists
        for rev in revisions:
            if rev.down_revision is not None:
                # The down_revision should be in our revisions list
                down_revs = (
                    rev.down_revision
                    if isinstance(rev.down_revision, tuple)
                    else (rev.down_revision,)
                )
                for down_rev in down_revs:
                    assert script.get_revision(down_rev) is not None, (
                        f"Migration {rev.revision} references non-existent "
                        f"down_revision {down_rev}"
                    )

    def test_head_is_reachable(self, alembic_config):
        """
        Verify we can traverse from base to head.

        What: Ensures migration path from base to head is unbroken
        Why: Broken path means new databases cannot be fully initialized
        """
        from alembic.script import ScriptDirectory

        script = ScriptDirectory.from_config(alembic_config)
        heads = script.get_heads()

        assert len(heads) == 1, f"Expected single head, got {heads}"
        assert heads[0] == "004", f"Expected head to be 004, got {heads[0]}"


class TestMigrationContent:
    """Tests for migration content correctness."""

    def test_initial_migration_creates_all_tables(self, alembic_config):
        """
        Verify initial migration (001) creates all expected tables.

        What: Checks that 001 migration creates the full schema
        Why: Missing tables would cause application failures
        """
        from alembic.script import ScriptDirectory

        script = ScriptDirectory.from_config(alembic_config)
        rev_001 = script.get_revision("001")

        # Read the migration file
        import importlib.util

        spec = importlib.util.spec_from_file_location("001", rev_001.path)
        module = importlib.util.module_from_spec(spec)
        spec.loader.exec_module(module)

        # Check upgrade function exists
        assert hasattr(module, "upgrade"), "Migration 001 missing upgrade function"
        assert hasattr(module, "downgrade"), "Migration 001 missing downgrade function"

        # Get source code to verify table creation
        import inspect as py_inspect

        source = py_inspect.getsource(module.upgrade)

        expected_tables = [
            "games",
            "plays",
            "lineups",
            "game_sessions",
            "rolls",
            "roster_links",
            "game_cardset_links",
        ]

        for table in expected_tables:
            assert (
                f"'{table}'" in source
            ), f"Migration 001 missing table creation for '{table}'"

    def test_materialized_views_migration_content(self, alembic_config):
        """
        Verify materialized views migration (004) creates expected views.

        What: Checks that 004 migration creates statistics views
        Why: Missing views would break box score functionality
        """
        from alembic.script import ScriptDirectory

        script = ScriptDirectory.from_config(alembic_config)
        rev_004 = script.get_revision("004")

        import importlib.util

        spec = importlib.util.spec_from_file_location("004", rev_004.path)
        module = importlib.util.module_from_spec(spec)
        spec.loader.exec_module(module)

        import inspect as py_inspect

        source = py_inspect.getsource(module.upgrade)

        expected_views = [
            "batting_game_stats",
            "pitching_game_stats",
            "game_stats",
        ]

        for view in expected_views:
            assert (
                view in source
            ), f"Migration 004 missing materialized view '{view}'"


class TestCurrentDatabaseState:
    """Tests for current database migration state."""

    def test_database_at_head(self, sync_engine):
        """
        Verify database is at the latest migration revision.

        What: Checks alembic_version table shows current head
        Why: Ensures database schema is up to date
        """
        with sync_engine.connect() as conn:
            result = conn.execute(text("SELECT version_num FROM alembic_version"))
            versions = list(result)

            assert len(versions) == 1, f"Expected 1 version row, got {len(versions)}"
            assert versions[0][0] == "004", f"Expected version 004, got {versions[0][0]}"

    def test_all_tables_exist(self, sync_engine):
        """
        Verify all expected tables exist in the database.

        What: Inspects database to confirm table presence
        Why: Validates migrations were applied correctly
        """
        inspector = inspect(sync_engine)
        tables = set(inspector.get_table_names())

        expected_tables = {
            "games",
            "plays",
            "lineups",
            "game_sessions",
            "rolls",
            "roster_links",
            "game_cardset_links",
            "alembic_version",  # Created by Alembic
        }

        missing = expected_tables - tables
        assert not missing, f"Missing tables: {missing}"

    def test_materialized_views_exist(self, sync_engine):
        """
        Verify all materialized views exist in the database.

        What: Checks for statistics views created by migration 004
        Why: Box score functionality depends on these views
        """
        with sync_engine.connect() as conn:
            result = conn.execute(
                text(
                    """
                SELECT matviewname FROM pg_matviews
                WHERE schemaname = 'public'
                ORDER BY matviewname
            """
                )
            )
            views = {row[0] for row in result}

        expected_views = {
            "batting_game_stats",
            "pitching_game_stats",
            "game_stats",
        }

        missing = expected_views - views
        assert not missing, f"Missing materialized views: {missing}"


class TestMigrationDowngrade:
    """Tests for migration downgrade safety (non-destructive checks only)."""

    def test_downgrade_functions_exist(self, alembic_config):
        """
        Verify all migrations have downgrade functions.

        What: Checks each migration has a downgrade() implementation
        Why: Rollback capability is critical for disaster recovery
        """
        from alembic.script import ScriptDirectory

        script = ScriptDirectory.from_config(alembic_config)

        for rev in script.walk_revisions():
            import importlib.util

            spec = importlib.util.spec_from_file_location(rev.revision, rev.path)
            module = importlib.util.module_from_spec(spec)
            spec.loader.exec_module(module)

            assert hasattr(
                module, "downgrade"
            ), f"Migration {rev.revision} missing downgrade function"

            import inspect as py_inspect

            source = py_inspect.getsource(module.downgrade)

            # Downgrade should not just be 'pass' for data-creating migrations
            if rev.revision in ["001", "004"]:
                assert "pass" not in source.replace(
                    " ", ""
                ).replace("\n", ""), (
                    f"Migration {rev.revision} downgrade should not be empty"
                )
