# tests/test_bulk_update.py
"""Tests for bulk_update() method."""

import pytest

from atomsql import Database, Model
from atomsql.orm.fields import BooleanField, CharField, IntegerField, TextField


class TestBulkUpdateBasic:
    """Basic bulk_update functionality tests."""

    def test_bulk_update_empty_list(self):
        """Test bulk_update with empty list returns empty list."""

        class User(Model):
            name = CharField(max_length=100)

        db = Database("sqlite:///:memory:")
        db.register(User)

        result = User.bulk_update([])
        assert result == []

    def test_bulk_update_single_instance(self):
        """Test bulk_update with a single instance."""

        class User(Model):
            name = CharField(max_length=100)
            email = CharField(max_length=150)

        db = Database("sqlite:///:memory:")
        db.register(User)

        # Create a user
        user = User.create(name="Alice", email="alice@old.com")
        original_id = user.id

        # Update the user
        user.email = "alice@new.com"
        result = User.bulk_update([user])

        assert len(result) == 1
        assert result[0].id == original_id
        assert result[0].email == "alice@new.com"

        # Verify in database
        updated = User.get(original_id)
        assert updated.email == "alice@new.com"

    def test_bulk_update_multiple_instances(self):
        """Test bulk_update with multiple instances."""

        class Product(Model):
            name = CharField(max_length=100)
            price = IntegerField()

        db = Database("sqlite:///:memory:")
        db.register(Product)

        # Create products
        products = [Product.create(name=f"Product {i}", price=i * 10) for i in range(5)]

        # Update prices
        for i, product in enumerate(products):
            product.price = (i + 1) * 100

        result = Product.bulk_update(products)

        assert len(result) == 5
        # Verify updates
        for i, product in enumerate(Product.filter().order_by("id").all()):
            assert product.price == (i + 1) * 100

    def test_bulk_update_specific_fields(self):
        """Test bulk_update with specific fields only."""

        class User(Model):
            name = CharField(max_length=100)
            email = CharField(max_length=150)
            age = IntegerField()

        db = Database("sqlite:///:memory:")
        db.register(User)

        # Create users
        users = [
            User.create(name=f"User{i}", email=f"user{i}@test.com", age=20 + i)
            for i in range(3)
        ]

        # Update only email (not name or age)
        original_names = [u.name for u in users]
        original_ages = [u.age for u in users]

        for i, user in enumerate(users):
            user.name = f"NewName{i}"  # This should NOT be updated
            user.email = f"new{i}@test.com"  # This should be updated
            user.age = 99  # This should NOT be updated

        User.bulk_update(users, fields=["email"])

        # Verify only email was updated
        # Query fresh instances bypassing identity map
        db._identity_map.clear()  # Clear identity map to get fresh data
        updated_users = User.filter().order_by("id").all()
        for i, user in enumerate(updated_users):
            assert user.email == f"new{i}@test.com"  # Updated
            assert user.name == original_names[i]  # Not updated
            assert user.age == original_ages[i]  # Not updated


class TestBulkUpdateValidation:
    """Validation tests for bulk_update."""

    def test_bulk_update_requires_id(self):
        """Test that bulk_update requires all instances to have IDs."""

        class User(Model):
            name = CharField(max_length=100)

        db = Database("sqlite:///:memory:")
        db.register(User)

        # Create one user with ID
        user1 = User.create(name="Alice")

        # Create instance without ID
        user2 = User(name="Bob")

        with pytest.raises(ValueError) as exc_info:
            User.bulk_update([user1, user2])

        assert "requires all instances to have an id" in str(exc_info.value)

    def test_bulk_update_requires_correct_type(self):
        """Test that bulk_update validates instance types."""

        class User(Model):
            name = CharField(max_length=100)

        class Product(Model):
            name = CharField(max_length=100)

        db = Database("sqlite:///:memory:")
        db.register(User)
        db.register(Product)

        user = User.create(name="Alice")
        product = Product.create(name="Widget")

        with pytest.raises(TypeError) as exc_info:
            User.bulk_update([user, product])

        assert "must be of type User" in str(exc_info.value)

    def test_bulk_update_invalid_field(self):
        """Test bulk_update with invalid field name."""

        class User(Model):
            name = CharField(max_length=100)

        db = Database("sqlite:///:memory:")
        db.register(User)

        user = User.create(name="Alice")

        with pytest.raises(ValueError) as exc_info:
            User.bulk_update([user], fields=["nonexistent"])

        assert "does not exist" in str(exc_info.value)

    def test_bulk_update_cannot_update_id_field(self):
        """Test that bulk_update prevents updating id field."""

        class User(Model):
            name = CharField(max_length=100)

        db = Database("sqlite:///:memory:")
        db.register(User)

        user = User.create(name="Alice")

        with pytest.raises(ValueError) as exc_info:
            User.bulk_update([user], fields=["id"])

        assert "Cannot update 'id' field" in str(exc_info.value)

    def test_bulk_update_requires_database_registration(self):
        """Test bulk_update fails if model not registered."""

        class User(Model):
            name = CharField(max_length=100)

        with pytest.raises(RuntimeError) as exc_info:
            User.bulk_update([])

        assert "not registered" in str(exc_info.value)


class TestBulkUpdateWithFields:
    """Test bulk_update with different field types."""

    def test_bulk_update_with_text_fields(self):
        """Test bulk_update with TEXT fields."""

        class Article(Model):
            title = CharField(max_length=200)
            content = TextField()

        db = Database("sqlite:///:memory:")
        db.register(Article)

        articles = [
            Article.create(title=f"Article {i}", content=f"Content {i}")
            for i in range(3)
        ]

        # Update content
        for i, article in enumerate(articles):
            article.content = f"Updated content {i}" * 100

        Article.bulk_update(articles, fields=["content"])

        # Verify
        for i, article in enumerate(Article.filter().order_by("id").all()):
            assert "Updated content" in article.content

    def test_bulk_update_with_boolean_fields(self):
        """Test bulk_update with boolean fields."""

        class Task(Model):
            title = CharField(max_length=100)
            completed = BooleanField(default=False)

        db = Database("sqlite:///:memory:")
        db.register(Task)

        tasks = [Task.create(title=f"Task {i}", completed=False) for i in range(5)]

        # Mark some as completed
        for i, task in enumerate(tasks):
            task.completed = i % 2 == 0

        Task.bulk_update(tasks, fields=["completed"])

        # Verify
        updated_tasks = Task.filter().order_by("id").all()
        for i, task in enumerate(updated_tasks):
            assert task.completed == (i % 2 == 0)

    def test_bulk_update_with_nullable_fields(self):
        """Test bulk_update with nullable fields."""

        class Profile(Model):
            username = CharField(max_length=100)
            bio = CharField(max_length=500, nullable=True)

        db = Database("sqlite:///:memory:")
        db.register(Profile)

        profiles = [
            Profile.create(username=f"user{i}", bio=f"Bio {i}") for i in range(3)
        ]

        # Set some bios to None
        for i, profile in enumerate(profiles):
            profile.bio = None if i % 2 == 0 else f"Updated bio {i}"

        Profile.bulk_update(profiles, fields=["bio"])

        # Verify
        for i, profile in enumerate(Profile.filter().order_by("id").all()):
            if i % 2 == 0:
                assert profile.bio is None
            else:
                assert profile.bio == f"Updated bio {i}"

    def test_bulk_update_all_fields(self):
        """Test bulk_update without specifying fields (updates all)."""

        class User(Model):
            name = CharField(max_length=100)
            email = CharField(max_length=150)
            age = IntegerField()

        db = Database("sqlite:///:memory:")
        db.register(User)

        users = [
            User.create(name=f"User{i}", email=f"user{i}@test.com", age=20 + i)
            for i in range(3)
        ]

        # Update all fields
        for i, user in enumerate(users):
            user.name = f"Updated{i}"
            user.email = f"updated{i}@test.com"
            user.age = 30 + i

        User.bulk_update(users)  # No fields parameter = update all

        # Verify all fields updated
        for i, user in enumerate(User.filter().order_by("id").all()):
            assert user.name == f"Updated{i}"
            assert user.email == f"updated{i}@test.com"
            assert user.age == 30 + i


class TestBulkUpdatePerformance:
    """Performance tests for bulk_update."""

    def test_bulk_update_large_batch(self):
        """Test bulk_update with large number of records."""

        class Item(Model):
            name = CharField(max_length=100)
            value = IntegerField()

        db = Database("sqlite:///:memory:")
        db.register(Item)

        # Create 100 items
        items = [Item.create(name=f"Item{i}", value=i) for i in range(100)]

        # Update all values
        for i, item in enumerate(items):
            item.value = i * 10

        result = Item.bulk_update(items, fields=["value"])

        assert len(result) == 100

        # Verify all updated
        count = Item.filter(value__gte=0).count()
        assert count == 100


class TestBulkUpdateQuerying:
    """Test querying after bulk_update."""

    def test_query_after_bulk_update(self):
        """Test that queries work correctly after bulk_update."""

        class Product(Model):
            name = CharField(max_length=100)
            price = IntegerField()
            in_stock = BooleanField(default=True)

        db = Database("sqlite:///:memory:")
        db.register(Product)

        products = [
            Product.create(name=f"Product {i}", price=100, in_stock=True)
            for i in range(10)
        ]

        # Mark half as out of stock
        for i, product in enumerate(products):
            product.in_stock = i < 5

        Product.bulk_update(products, fields=["in_stock"])

        # Query in stock products
        in_stock = Product.filter(in_stock=True).count()
        assert in_stock == 5

        out_of_stock = Product.filter(in_stock=False).count()
        assert out_of_stock == 5


class TestBulkUpdateEdgeCases:
    """Edge case tests for bulk_update."""

    def test_bulk_update_in_transaction(self):
        """Test bulk_update within a transaction."""

        class User(Model):
            name = CharField(max_length=100)
            status = CharField(max_length=50)

        db = Database("sqlite:///:memory:")
        db.register(User)

        users = [User.create(name=f"User{i}", status="active") for i in range(3)]

        # Update within transaction
        with db.transaction():
            for user in users:
                user.status = "inactive"
            User.bulk_update(users, fields=["status"])

        # Verify all updated
        for user in User.filter().all():
            assert user.status == "inactive"

    def test_bulk_update_transaction_rollback(self):
        """Test that bulk_update can be rolled back."""

        class User(Model):
            name = CharField(max_length=100)
            status = CharField(max_length=50)

        db = Database("sqlite:///:memory:")
        db.register(User)

        users = [User.create(name=f"User{i}", status="active") for i in range(3)]
        user_ids = [u.id for u in users]

        try:
            with db.transaction():
                for user in users:
                    user.status = "inactive"
                User.bulk_update(users, fields=["status"])
                raise Exception("Force rollback")
        except Exception:
            pass

        # Verify rollback worked - all still active
        # Clear identity map and query fresh instances from database
        db._identity_map.clear()
        for user_id in user_ids:
            user = User.get(user_id)
            assert user.status == "active"

    def test_bulk_update_mixed_with_regular_update(self):
        """Test mixing bulk_update with regular updates."""

        class User(Model):
            name = CharField(max_length=100)
            score = IntegerField()

        db = Database("sqlite:///:memory:")
        db.register(User)

        # Regular create
        user1 = User.create(name="User1", score=10)
        user2 = User.create(name="User2", score=20)
        user3 = User.create(name="User3", score=30)

        # Bulk update two users
        user1.score = 100
        user2.score = 200
        User.bulk_update([user1, user2], fields=["score"])

        # Regular update one user
        user3.score = 300
        user3.save()

        # Verify all
        users = {u.name: u.score for u in User.filter().all()}
        assert users["User1"] == 100
        assert users["User2"] == 200
        assert users["User3"] == 300


if __name__ == "__main__":
    pytest.main([__file__, "-v"])
