import pytest
from atomsql import Database, Model, StringField, IntegerField
from atomsql.migrations import Migration, Migrator, SchemaDiff


class UserModel(Model):
    name = StringField()
    age = IntegerField()


@pytest.fixture
def db():
    """Create an in-memory database."""
    database = Database("sqlite:///:memory:")
    yield database
    database.close()


@pytest.fixture
def migrator(db):
    """Create a migrator instance."""
    return Migrator(db)


def test_migrations_table_created(migrator):
    """Test that migrations table is created."""
    result = migrator.db.execute(
        "SELECT name FROM sqlite_master WHERE type='table' AND name='_migrations'"
    )
    assert result.fetchone() is not None


def test_add_migration(migrator):
    """Test adding a migration."""

    def up(db):
        db.execute("CREATE TABLE test (id INTEGER PRIMARY KEY, name TEXT)")

    def down(db):
        db.execute("DROP TABLE test")

    migration = Migration("001", "create_test_table", up=up, down=down)
    migrator.add_migration(migration)

    assert len(migrator.migrations) == 1
    assert migrator.migrations[0].version == "001"


def test_apply_migration(migrator):
    """Test applying a migration."""

    def up(db):
        db.execute("CREATE TABLE test (id INTEGER PRIMARY KEY, name TEXT)")

    def down(db):
        db.execute("DROP TABLE test")

    migration = Migration("001", "create_test_table", up=up, down=down)
    migrator.add_migration(migration)
    migrator.migrate()

    # Check migration was applied
    applied = migrator.get_applied_migrations()
    assert "001" in applied

    # Check table was created
    result = migrator.db.execute(
        "SELECT name FROM sqlite_master WHERE type='table' AND name='test'"
    )
    assert result.fetchone() is not None


def test_rollback_migration(migrator):
    """Test rolling back a migration."""

    def up(db):
        db.execute("CREATE TABLE test (id INTEGER PRIMARY KEY, name TEXT)")

    def down(db):
        db.execute("DROP TABLE test")

    migration = Migration("001", "create_test_table", up=up, down=down)
    migrator.add_migration(migration)
    migrator.migrate()

    # Rollback
    migrator.rollback()

    # Check migration was removed
    applied = migrator.get_applied_migrations()
    assert "001" not in applied

    # Check table was dropped
    result = migrator.db.execute(
        "SELECT name FROM sqlite_master WHERE type='table' AND name='test'"
    )
    assert result.fetchone() is None


def test_multiple_migrations(migrator):
    """Test applying multiple migrations in order."""

    def up1(db):
        db.execute("CREATE TABLE test1 (id INTEGER PRIMARY KEY)")

    def down1(db):
        db.execute("DROP TABLE test1")

    def up2(db):
        db.execute("CREATE TABLE test2 (id INTEGER PRIMARY KEY)")

    def down2(db):
        db.execute("DROP TABLE test2")

    migration1 = Migration("001", "create_test1", up=up1, down=down1)
    migration2 = Migration("002", "create_test2", up=up2, down=down2)

    migrator.add_migration(migration1)
    migrator.add_migration(migration2)
    migrator.migrate()

    applied = migrator.get_applied_migrations()
    assert "001" in applied
    assert "002" in applied
    assert applied.index("001") < applied.index("002")


def test_get_pending_migrations(migrator):
    """Test getting pending migrations."""

    def up(db):
        pass

    def down(db):
        pass

    migration1 = Migration("001", "test1", up=up, down=down)
    migration2 = Migration("002", "test2", up=up, down=down)

    migrator.add_migration(migration1)
    migrator.add_migration(migration2)

    # Apply first migration only
    migration1.apply(migrator.db)
    migrator.db.execute(
        "INSERT INTO _migrations (version, name, applied_at) VALUES ('001', 'test1', datetime('now'))"
    )

    pending = migrator.get_pending_migrations()
    assert len(pending) == 1
    assert pending[0].version == "002"


def test_schema_diff_new_table(db):
    """Test detecting a new table in models."""
    # Don't register yet, just add to ModelMeta manually for testing
    from atomsql.orm.models import ModelMeta

    # Temporarily add UserModel to models list
    if UserModel not in ModelMeta.models:
        ModelMeta.models.append(UserModel)

    try:
        # Create schema diff without creating the table
        diff_tool = SchemaDiff(db)
        diff = diff_tool.diff()

        # UserModel table doesn't exist in DB yet
        assert "usermodel" in diff["tables_to_create"]
    finally:
        # Clean up
        if UserModel in ModelMeta.models:
            ModelMeta.models.remove(UserModel)


def test_schema_diff_no_changes(db):
    """Test schema diff when models match database."""
    from atomsql.orm.models import ModelMeta

    # Create the table manually
    db.execute(
        "CREATE TABLE usermodel (id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT, age INTEGER)"
    )

    # Clear and add only our model to ModelMeta
    original_models = ModelMeta.models.copy()
    ModelMeta.models.clear()
    ModelMeta.models.append(UserModel)

    try:
        diff_tool = SchemaDiff(db)
        diff = diff_tool.diff()

        assert len(diff["tables_to_create"]) == 0
        assert len(diff["tables_to_drop"]) == 0
        assert len(diff["columns_to_add"]) == 0
    finally:
        ModelMeta.models.clear()
        ModelMeta.models.extend(original_models)


def test_schema_diff_new_column(db):
    """Test detecting a new column."""
    from atomsql.orm.models import ModelMeta

    # Create table without age column
    db.execute("CREATE TABLE usermodel (id INTEGER PRIMARY KEY, name TEXT)")

    # Clear and add only our model to ModelMeta
    original_models = ModelMeta.models.copy()
    ModelMeta.models.clear()
    ModelMeta.models.append(UserModel)

    try:
        diff_tool = SchemaDiff(db)
        diff = diff_tool.diff()

        assert "usermodel" in diff["columns_to_add"]
        col_names = [col.name for col in diff["columns_to_add"]["usermodel"]]
        assert "age" in col_names
    finally:
        ModelMeta.models.clear()
        ModelMeta.models.extend(original_models)


def test_schema_diff_dropped_column(db):
    """Test detecting a dropped column."""
    from atomsql.orm.models import ModelMeta

    # Create table with extra column
    db.execute(
        "CREATE TABLE usermodel (id INTEGER PRIMARY KEY, name TEXT, age INTEGER, extra TEXT)"
    )

    # Clear and add only our model to ModelMeta
    original_models = ModelMeta.models.copy()
    ModelMeta.models.clear()
    ModelMeta.models.append(UserModel)

    try:
        diff_tool = SchemaDiff(db)
        diff = diff_tool.diff()

        assert "usermodel" in diff["columns_to_drop"]
        assert "extra" in diff["columns_to_drop"]["usermodel"]
    finally:
        ModelMeta.models.clear()
        ModelMeta.models.extend(original_models)


def test_migration_sorting(migrator):
    """Test that migrations are sorted by version."""

    def up(db):
        pass

    def down(db):
        pass

    migration1 = Migration("003", "test", up=up, down=down)
    migration2 = Migration("001", "test", up=up, down=down)
    migration3 = Migration("002", "test", up=up, down=down)

    migrator.add_migration(migration1)
    migrator.add_migration(migration2)
    migrator.add_migration(migration3)

    versions = [m.version for m in migrator.migrations]
    assert versions == ["001", "002", "003"]


def test_migration_target_version(migrator):
    """Test migrating to a specific target version."""

    def up(db):
        pass

    def down(db):
        pass

    migration1 = Migration("001", "test1", up=up, down=down)
    migration2 = Migration("002", "test2", up=up, down=down)
    migration3 = Migration("003", "test3", up=up, down=down)

    migrator.add_migration(migration1)
    migrator.add_migration(migration2)
    migrator.add_migration(migration3)

    # Migrate only to version 002
    migrator.migrate(target="002")

    applied = migrator.get_applied_migrations()
    assert "001" in applied
    assert "002" in applied
    assert "003" not in applied
