# tests/test_bulk_create.py
import pytest
from atomsql import Database, Model
from atomsql.orm.fields import CharField, IntegerField, BooleanField, TextField


class TestBulkCreateBasic:
    """Test basic bulk_create functionality."""

    def test_bulk_create_empty_list(self):
        """Test bulk_create with empty list returns empty list."""

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

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

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

    def test_bulk_create_single_instance(self):
        """Test bulk_create with single instance."""

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

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

        users = [User(name="Alice")]
        result = User.bulk_create(users)

        assert len(result) == 1
        assert result[0].id is not None
        assert result[0].name == "Alice"

    def test_bulk_create_multiple_instances(self):
        """Test bulk_create with multiple instances."""

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

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

        users = [User(name="Alice"), User(name="Bob"), User(name="Charlie")]
        result = User.bulk_create(users)

        assert len(result) == 3
        assert all(user.id is not None for user in result)
        assert result[0].name == "Alice"
        assert result[1].name == "Bob"
        assert result[2].name == "Charlie"

    def test_bulk_create_ids_are_sequential(self):
        """Test that IDs assigned by bulk_create are sequential."""

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

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

        users = [
            User(name="User1"),
            User(name="User2"),
            User(name="User3"),
            User(name="User4"),
        ]
        result = User.bulk_create(users)

        ids = [user.id for user in result]
        assert ids == sorted(ids)  # Should be in order
        # Check they're consecutive
        for i in range(len(ids) - 1):
            assert ids[i + 1] == ids[i] + 1

    def test_bulk_create_persists_to_database(self):
        """Test that bulk_create actually saves to database."""

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

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

        users = [User(name="Alice"), User(name="Bob")]
        User.bulk_create(users)

        # Verify by querying
        all_users = list(User.filter())
        assert len(all_users) == 2
        names = {user.name for user in all_users}
        assert names == {"Alice", "Bob"}


class TestBulkCreateWithFields:
    """Test bulk_create with various field types."""

    def test_bulk_create_with_multiple_fields(self):
        """Test bulk_create with multiple field types."""

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

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

        products = [
            Product(name="Widget", price=10, in_stock=True),
            Product(name="Gadget", price=20, in_stock=False),
            Product(name="Doohickey", price=15, in_stock=True),
        ]
        result = Product.bulk_create(products)

        assert len(result) == 3
        assert all(p.id is not None for p in result)

        # Verify data persisted correctly
        retrieved = list(Product.filter())
        assert len(retrieved) == 3
        widget = next(p for p in retrieved if p.name == "Widget")
        assert widget.price == 10
        assert widget.in_stock is True

    def test_bulk_create_with_default_values(self):
        """Test bulk_create respects default field values."""

        class Article(Model):
            title = CharField(max_length=200)
            view_count = IntegerField(default=0)
            published = BooleanField(default=False)

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

        articles = [
            Article(title="Article 1"),
            Article(title="Article 2", view_count=5),
            Article(title="Article 3", published=True),
        ]
        result = Article.bulk_create(articles)

        assert result[0].view_count == 0
        assert result[0].published is False
        assert result[1].view_count == 5
        assert result[2].published is True

    def test_bulk_create_with_nullable_fields(self):
        """Test bulk_create with nullable fields."""

        class Comment(Model):
            text = TextField()
            author = CharField(max_length=100, nullable=True)

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

        comments = [
            Comment(text="Great post!", author="Alice"),
            Comment(text="Anonymous comment", author=None),
            Comment(text="Another comment", author="Bob"),
        ]
        result = Comment.bulk_create(comments)

        assert len(result) == 3
        assert result[0].author == "Alice"
        assert result[1].author is None
        assert result[2].author == "Bob"

    def test_bulk_create_with_text_fields(self):
        """Test bulk_create with text fields."""

        class Task(Model):
            title = CharField(max_length=100)
            description = TextField()
            priority = CharField(max_length=10)

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

        tasks = [
            Task(title="Task 1", description="Description 1", priority="low"),
            Task(title="Task 2", description="Description 2", priority="high"),
            Task(title="Task 3", description="Description 3", priority="medium"),
        ]
        result = Task.bulk_create(tasks)

        assert len(result) == 3
        assert all(t.id is not None for t in result)

        # Verify priorities
        retrieved = list(Task.filter())
        priorities = {t.title: t.priority for t in retrieved}
        assert priorities["Task 1"] == "low"
        assert priorities["Task 2"] == "high"
        assert priorities["Task 3"] == "medium"


class TestBulkCreateValidation:
    """Test bulk_create validation and error handling."""

    def test_bulk_create_requires_correct_type(self):
        """Test that all instances must be of the correct type."""

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

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

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

        users = [User(name="Alice"), Product(title="Widget")]

        with pytest.raises(TypeError) as exc_info:
            User.bulk_create(users)
        assert "must be of type User" in str(exc_info.value)

    def test_bulk_create_rejects_instances_with_id(self):
        """Test that bulk_create rejects instances that already have an ID."""

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

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

        # Create a user to get an ID
        existing = User.create(name="Existing")

        # Try to bulk_create with an instance that has ID
        users = [User(name="New"), existing]

        with pytest.raises(ValueError) as exc_info:
            User.bulk_create(users)
        assert "already have an id" in str(exc_info.value)

    def test_bulk_create_validates_required_fields(self):
        """Test that field validation happens during instance creation."""

        class Task(Model):
            title = CharField(max_length=100, nullable=False)
            status = CharField(max_length=20)

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

        # Validation happens when creating the instance, not during bulk_create
        with pytest.raises(ValueError) as exc_info:
            [
                Task(title="Task 1", status="todo"),
                Task(title=None, status="done"),  # Invalid - title is required
            ]
        assert "expected a str" in str(exc_info.value) or "cannot be None" in str(
            exc_info.value
        )

    def test_bulk_create_requires_database_registration(self):
        """Test that bulk_create fails if model not registered."""

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

        users = [User(name="Alice")]

        with pytest.raises(RuntimeError) as exc_info:
            User.bulk_create(users)
        assert "not registered to any database" in str(exc_info.value)


class TestBulkCreatePerformance:
    """Test bulk_create performance characteristics."""

    def test_bulk_create_large_batch(self):
        """Test bulk_create with a large number of records."""

        class LogEntry(Model):
            message = CharField(max_length=200)
            level = IntegerField()

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

        # Create 100 log entries
        entries = [LogEntry(message=f"Log {i}", level=i % 5) for i in range(100)]
        result = LogEntry.bulk_create(entries)

        assert len(result) == 100
        assert all(entry.id is not None for entry in result)

        # Verify all were inserted
        count = len(list(LogEntry.filter()))
        assert count == 100

    def test_bulk_create_vs_individual_create(self):
        """Test that bulk_create assigns IDs correctly."""

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

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

        # Create some individual users first
        User.create(name="Individual 1")
        User.create(name="Individual 2")

        # Now bulk create
        bulk_users = [User(name="Bulk 1"), User(name="Bulk 2"), User(name="Bulk 3")]
        result = User.bulk_create(bulk_users)

        # IDs should be after the individual ones
        assert all(user.id > 2 for user in result)

        # Total should be 5
        total = len(list(User.filter()))
        assert total == 5


class TestBulkCreateIdentityMap:
    """Test bulk_create interaction with identity map."""

    def test_bulk_create_adds_to_identity_map(self):
        """Test that bulk_create adds instances to identity map."""

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

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

        users = [User(name="Alice"), User(name="Bob")]
        result = User.bulk_create(users)

        # Instances should be in identity map
        for user in result:
            cached = db.get_from_identity_map(User, user.id)
            assert cached is user  # Should be the same instance

    def test_bulk_create_followed_by_get(self):
        """Test getting instances after bulk_create uses identity map."""

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

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

        users = [User(name="Alice"), User(name="Bob")]
        created = User.bulk_create(users)

        # Get should return same instances from identity map
        retrieved_alice = User.get(created[0].id)
        assert retrieved_alice is created[0]


class TestBulkCreateQuerying:
    """Test querying after bulk_create."""

    def test_filter_after_bulk_create(self):
        """Test filtering records created with bulk_create."""

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

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

        products = [
            Product(name="Widget", category="tools"),
            Product(name="Gadget", category="electronics"),
            Product(name="Doohickey", category="tools"),
        ]
        Product.bulk_create(products)

        # Filter by category
        tools = list(Product.filter(category="tools"))
        assert len(tools) == 2
        names = {p.name for p in tools}
        assert names == {"Widget", "Doohickey"}

    def test_order_after_bulk_create(self):
        """Test ordering records created with bulk_create."""

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

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

        users = [
            User(name="Charlie", age=30),
            User(name="Alice", age=25),
            User(name="Bob", age=35),
        ]
        User.bulk_create(users)

        # Order by age
        ordered = list(User.filter().order_by("age"))
        assert ordered[0].name == "Alice"
        assert ordered[1].name == "Charlie"
        assert ordered[2].name == "Bob"

    def test_count_after_bulk_create(self):
        """Test count aggregation after bulk_create."""

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

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

        items = [Item(name=f"Item {i}") for i in range(10)]
        Item.bulk_create(items)

        count = Item.filter().count()
        assert count == 10


class TestBulkCreateEdgeCases:
    """Test edge cases and special scenarios."""

    def test_bulk_create_mixed_with_regular_create(self):
        """Test mixing bulk_create and regular create."""

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

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

        # Regular create
        User.create(name="Regular 1")

        # Bulk create
        bulk = [User(name="Bulk 1"), User(name="Bulk 2")]
        User.bulk_create(bulk)

        # Another regular create
        User.create(name="Regular 2")

        # More bulk create
        more_bulk = [User(name="Bulk 3")]
        User.bulk_create(more_bulk)

        # All should be in database
        all_users = list(User.filter())
        assert len(all_users) == 5

    def test_bulk_create_in_transaction(self):
        """Test bulk_create within a transaction."""

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

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

        with db.transaction():
            users = [User(name="Alice"), User(name="Bob")]
            result = User.bulk_create(users)
            assert len(result) == 2

        # Should be committed
        count = len(list(User.filter()))
        assert count == 2

    def test_bulk_create_returns_same_instances(self):
        """Test that bulk_create returns the same instances passed in."""

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

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

        users = [User(name="Alice"), User(name="Bob")]
        result = User.bulk_create(users)

        # Should be the same objects, just with IDs populated
        assert result[0] is users[0]
        assert result[1] is users[1]
