import pytest
from atomsql.orm.models import Model
from atomsql.orm.fields import CharField, IntegerField, BooleanField
from atomsql.core.db import Database


class User(Model):
    """Test model for update operations."""

    _table_name = "users"

    name = CharField(max_length=100)
    age = IntegerField()
    active = BooleanField(default=True)
    email = CharField(max_length=255)


class Product(Model):
    """Test model with various field types."""

    _table_name = "products"

    name = CharField(max_length=100)
    price = IntegerField()
    in_stock = BooleanField(default=True)
    category = CharField(max_length=50)


@pytest.fixture
def db():
    """Create in-memory SQLite database for testing."""
    database = Database("sqlite:///:memory:")
    database.register(User)
    database.register(Product)

    yield database


class TestQuerySetUpdate:
    """Test QuerySet update() method functionality."""

    def test_update_basic(self, db):
        """Test basic update functionality."""
        # Create test users
        User.create(name="Alice", age=25, active=True, email="alice@test.com")
        User.create(name="Bob", age=30, active=False, email="bob@test.com")

        # Update single field
        updated_count = User.filter(name="Alice").update(age=26)

        assert updated_count == 1

        # Verify update
        alice = User.filter(name="Alice").first()
        assert alice.age == 26

    def test_update_multiple_fields(self, db):
        """Test updating multiple fields at once."""
        # Create test user
        User.create(name="Charlie", age=35, active=False, email="charlie@test.com")

        # Update multiple fields
        updated_count = User.filter(name="Charlie").update(
            age=40, active=True, email="charlie.new@test.com"
        )

        assert updated_count == 1

        # Verify updates
        charlie = User.filter(name="Charlie").first()
        assert charlie.age == 40
        assert charlie.active is True
        assert charlie.email == "charlie.new@test.com"

    def test_update_multiple_records(self, db):
        """Test updating multiple records with same filter."""
        # Create test users
        User.create(name="User1", age=20, active=False, email="user1@test.com")
        User.create(name="User2", age=25, active=False, email="user2@test.com")
        User.create(name="User3", age=30, active=True, email="user3@test.com")

        # Update all inactive users
        updated_count = User.filter(active=False).update(active=True)

        assert updated_count == 2

        # Verify all users are now active
        active_count = User.filter(active=True).count()
        assert active_count == 3

    def test_update_with_complex_filters(self, db):
        """Test update with complex filter conditions."""
        # Create test users
        User.create(name="Young1", age=18, active=True, email="young1@test.com")
        User.create(name="Young2", age=22, active=True, email="young2@test.com")
        User.create(name="Old1", age=35, active=True, email="old1@test.com")
        User.create(name="Old2", age=45, active=False, email="old2@test.com")

        # Update users older than 30 and active
        updated_count = User.filter(age__gt=30, active=True).update(active=False)

        assert updated_count == 1

        # Verify only Old1 was updated
        old1 = User.filter(name="Old1").first()
        assert old1.active is False

        # Verify others unchanged
        young1 = User.filter(name="Young1").first()
        assert young1.active is True

    def test_update_no_matches(self, db):
        """Test update when no records match the filter."""
        # Create test user
        User.create(name="TestUser", age=25, active=True, email="test@test.com")

        # Try to update non-existent record
        updated_count = User.filter(name="NonExistent").update(age=30)

        assert updated_count == 0

    def test_update_empty_values(self, db):
        """Test update with empty values dictionary."""
        # Create test user
        User.create(name="TestUser", age=25, active=True, email="test@test.com")

        # Try to update with no values
        updated_count = User.filter(name="TestUser").update()

        assert updated_count == 0

    def test_update_invalid_field(self, db):
        """Test update with invalid field name."""
        # Create test user
        User.create(name="TestUser", age=25, active=True, email="test@test.com")

        # Try to update invalid field
        with pytest.raises(ValueError, match="Field 'invalid_field' does not exist"):
            User.filter(name="TestUser").update(invalid_field="value")

    def test_update_with_different_types(self, db):
        """Test update with different field types."""
        # Create test product
        Product.create(name="Widget", price=100, in_stock=True, category="Tools")

        # Update different field types
        updated_count = Product.filter(name="Widget").update(
            price=150,  # IntegerField
            in_stock=False,  # BooleanField
            category="Electronics",  # CharField
        )

        assert updated_count == 1

        # Verify updates
        widget = Product.filter(name="Widget").first()
        assert widget.price == 150
        assert widget.in_stock is False
        assert widget.category == "Electronics"

    def test_update_with_filter_chaining(self, db):
        """Test update with multiple chained filters."""
        # Create test users
        User.create(name="User1", age=25, active=True, email="user1@test.com")
        User.create(name="User2", age=35, active=True, email="user2@test.com")
        User.create(name="User3", age=25, active=False, email="user3@test.com")

        # Update with chained filters
        updated_count = (
            User.filter(age=25).filter(active=True).update(email="updated@test.com")
        )

        assert updated_count == 1

        # Verify correct user was updated
        user1 = User.filter(name="User1").first()
        assert user1.email == "updated@test.com"

        # Verify others unchanged
        user3 = User.filter(name="User3").first()
        assert user3.email == "user3@test.com"

    @pytest.mark.skip(reason="SQLite in-memory transaction rollback behavior")
    def test_update_with_transaction_rollback(self, db):
        """Test update within transaction that gets rolled back."""
        # Create test user
        User.create(name="TestUser", age=25, active=True, email="test@test.com")

        try:
            with db.transaction():
                # Update within transaction
                updated_count = User.filter(name="TestUser").update(age=30)
                assert updated_count == 1

                # Verify update within transaction
                user = User.filter(name="TestUser").first()
                assert user.age == 30

                # Force rollback
                raise Exception("Force rollback")
        except Exception:
            pass

        # Verify rollback - age should be original value
        user = User.filter(name="TestUser").first()
        assert user.age == 25

    def test_update_with_transaction_commit(self, db):
        """Test update within transaction that commits successfully."""
        # Create test user
        User.create(name="TestUser", age=25, active=True, email="test@test.com")

        with db.transaction():
            # Update within transaction
            updated_count = User.filter(name="TestUser").update(age=35)
            assert updated_count == 1

        # Verify commit - age should be updated value
        user = User.filter(name="TestUser").first()
        assert user.age == 35

    def test_update_performance_vs_individual(self, db):
        """Test update performance compared to individual updates."""
        import time

        # Create many test records
        users = []
        for i in range(100):
            users.append(
                User.create(
                    name=f"User{i}", age=20, active=False, email=f"user{i}@test.com"
                )
            )

        # Time individual updates
        start_time = time.time()
        for user in users:
            User.filter(id=user.id).update(active=True)
        individual_time = time.time() - start_time

        # Reset for bulk update test
        User.filter().update(active=False)

        # Time bulk update
        start_time = time.time()
        User.filter(active=False).update(active=True)
        bulk_time = time.time() - start_time

        # Bulk should be significantly faster
        assert bulk_time < individual_time

        # Verify all records updated
        active_count = User.filter(active=True).count()
        assert active_count == 100

    def test_update_return_count_accuracy(self, db):
        """Test that update returns accurate count of updated records."""
        # Create test records with different values
        User.create(name="Active1", age=25, active=True, email="active1@test.com")
        User.create(name="Active2", age=30, active=True, email="active2@test.com")
        User.create(name="Inactive1", age=35, active=False, email="inactive1@test.com")
        User.create(name="Inactive2", age=40, active=False, email="inactive2@test.com")
        User.create(name="Inactive3", age=45, active=False, email="inactive3@test.com")

        # Update only active users
        updated_count = User.filter(active=True).update(age=100)
        assert updated_count == 2

        # Update only inactive users
        updated_count = User.filter(active=False).update(age=200)
        assert updated_count == 3

        # Update all users (should be 5 total)
        updated_count = User.filter().update(email="updated@test.com")
        assert updated_count == 5
