# tests/test_bulk_update_postgres.py
"""PostgreSQL-specific tests for bulk_update() method."""

import pytest

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

pytest.importorskip("psycopg")


@pytest.fixture(autouse=True)
def reset_models():
    """Reset model registry before each test."""
    ModelMeta.models.clear()
    yield
    ModelMeta.models.clear()


@pytest.fixture
def postgres_db():
    """Create a PostgreSQL test database."""
    try:
        db = Database(
            "postgresql://atomsql_user:yourpassword@localhost:5432/atomsql_test"
        )

        # Drop all existing tables before starting tests
        tables = db.backend.get_tables()
        if tables:
            for table in tables:
                db.backend.execute(f'DROP TABLE IF EXISTS "{table}" CASCADE')
            db.backend.commit()

        yield db

        # Cleanup after test
        db.close()
    except Exception as e:
        pytest.skip(f"PostgreSQL not available: {e}")


class TestPostgresBulkUpdate:
    """Test bulk_update with PostgreSQL-specific features."""

    def test_postgres_bulk_update_basic(self, postgres_db):
        """Test basic bulk_update on PostgreSQL."""

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

        postgres_db.register(User)

        # Create users
        users = User.bulk_create(
            [
                User(name="Alice", status="active"),
                User(name="Bob", status="active"),
                User(name="Charlie", status="active"),
            ]
        )

        # Update statuses
        for user in users:
            user.status = "inactive"

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

        assert len(result) == 3

        # Verify in database
        postgres_db._identity_map.clear()
        for user in users:
            db_user = User.get(user.id)
            assert db_user.status == "inactive"

    def test_postgres_bulk_update_multiple_fields(self, postgres_db):
        """Test bulk_update with multiple fields on PostgreSQL."""

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

        postgres_db.register(Product)

        products = Product.bulk_create(
            [
                Product(name="Widget", price=100, stock=50),
                Product(name="Gadget", price=200, stock=30),
            ]
        )

        # Update both price and stock
        products[0].price = 150
        products[0].stock = 40
        products[1].price = 250
        products[1].stock = 20

        result = Product.bulk_update(products, fields=["price", "stock"])

        assert len(result) == 2

        postgres_db._identity_map.clear()
        widget = Product.get(products[0].id)
        assert widget.price == 150
        assert widget.stock == 40

        gadget = Product.get(products[1].id)
        assert gadget.price == 250
        assert gadget.stock == 20

    def test_postgres_bulk_update_large_batch(self, postgres_db):
        """Test bulk_update with large batch on PostgreSQL."""

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

        postgres_db.register(Item)

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

        # Update all values
        for item in items:
            item.value = item.value * 2

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

        assert len(result) == 100

        # Spot check some values
        postgres_db._identity_map.clear()
        item_50 = Item.get(items[50].id)
        assert item_50.value == 100  # 50 * 2

    def test_postgres_bulk_update_with_transaction_commit(self, postgres_db):
        """Test bulk_update commits properly in transaction."""

        class Account(Model):
            name = CharField(max_length=100)
            balance = IntegerField()

        postgres_db.register(Account)

        accounts = Account.bulk_create(
            [
                Account(name="Account A", balance=1000),
                Account(name="Account B", balance=2000),
            ]
        )

        # Transfer 500 from A to B in transaction
        with postgres_db.transaction():
            accounts[0].balance = 500
            accounts[1].balance = 2500
            Account.bulk_update(accounts, fields=["balance"])

        # Verify committed
        postgres_db._identity_map.clear()
        acc_a = Account.get(accounts[0].id)
        acc_b = Account.get(accounts[1].id)
        assert acc_a.balance == 500
        assert acc_b.balance == 2500

    def test_postgres_bulk_update_transaction_rollback(self, postgres_db):
        """Test bulk_update rollback on PostgreSQL transaction failure."""

        class Inventory(Model):
            item = CharField(max_length=100)
            quantity = IntegerField()

        postgres_db.register(Inventory)

        items = Inventory.bulk_create(
            [
                Inventory(item="Apples", quantity=100),
                Inventory(item="Oranges", quantity=50),
            ]
        )

        original_qty_0 = items[0].quantity
        original_qty_1 = items[1].quantity

        # Update in transaction then rollback
        try:
            with postgres_db.transaction():
                items[0].quantity = 200
                items[1].quantity = 100
                Inventory.bulk_update(items, fields=["quantity"])
                raise Exception("Force rollback")
        except Exception:
            pass

        # Verify rollback
        postgres_db._identity_map.clear()
        apples = Inventory.get(items[0].id)
        oranges = Inventory.get(items[1].id)
        assert apples.quantity == original_qty_0
        assert oranges.quantity == original_qty_1

    def test_postgres_bulk_update_with_boolean(self, postgres_db):
        """Test bulk_update with BooleanField on PostgreSQL."""

        class Feature(Model):
            name = CharField(max_length=100)
            enabled = BooleanField()

        postgres_db.register(Feature)

        features = Feature.bulk_create(
            [
                Feature(name="Feature A", enabled=True),
                Feature(name="Feature B", enabled=False),
                Feature(name="Feature C", enabled=True),
            ]
        )

        # Toggle all features
        for feature in features:
            feature.enabled = not feature.enabled

        result = Feature.bulk_update(features, fields=["enabled"])

        assert len(result) == 3

        postgres_db._identity_map.clear()
        feat_a = Feature.get(features[0].id)
        feat_b = Feature.get(features[1].id)
        feat_c = Feature.get(features[2].id)

        assert feat_a.enabled is False
        assert feat_b.enabled is True
        assert feat_c.enabled is False

    def test_postgres_bulk_update_with_text(self, postgres_db):
        """Test bulk_update with TextField on PostgreSQL."""

        class Note(Model):
            title = CharField(max_length=100)
            content = TextField()

        postgres_db.register(Note)

        notes = Note.bulk_create(
            [
                Note(title="Note 1", content="Original content 1"),
                Note(title="Note 2", content="Original content 2"),
            ]
        )

        # Update content
        notes[0].content = "Updated content 1 with more text"
        notes[1].content = "Updated content 2 with additional information"

        result = Note.bulk_update(notes, fields=["content"])

        assert len(result) == 2

        postgres_db._identity_map.clear()
        note1 = Note.get(notes[0].id)
        note2 = Note.get(notes[1].id)

        assert note1.content == "Updated content 1 with more text"
        assert note2.content == "Updated content 2 with additional information"

    def test_postgres_bulk_update_without_fields_updates_all(self, postgres_db):
        """Test bulk_update without fields parameter updates all fields."""

        class Profile(Model):
            username = CharField(max_length=100)
            bio = CharField(max_length=200)
            age = IntegerField()

        postgres_db.register(Profile)

        profiles = Profile.bulk_create(
            [
                Profile(username="user1", bio="Bio 1", age=25),
                Profile(username="user2", bio="Bio 2", age=30),
            ]
        )

        # Update all fields
        profiles[0].bio = "Updated Bio 1"
        profiles[0].age = 26
        profiles[1].bio = "Updated Bio 2"
        profiles[1].age = 31

        result = Profile.bulk_update(profiles)  # No fields specified

        assert len(result) == 2

        postgres_db._identity_map.clear()
        p1 = Profile.get(profiles[0].id)
        p2 = Profile.get(profiles[1].id)

        # All fields should be updated
        assert p1.bio == "Updated Bio 1"
        assert p1.age == 26
        assert p2.bio == "Updated Bio 2"
        assert p2.age == 31

    def test_postgres_bulk_update_case_when_syntax(self, postgres_db):
        """Test that bulk_update generates correct CASE/WHEN SQL for PostgreSQL."""

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

        postgres_db.register(Counter)

        counters = Counter.bulk_create(
            [
                Counter(name="A", value=1),
                Counter(name="B", value=2),
                Counter(name="C", value=3),
            ]
        )

        # Set different values for each
        counters[0].value = 10
        counters[1].value = 20
        counters[2].value = 30

        result = Counter.bulk_update(counters, fields=["value"])

        assert len(result) == 3

        # Verify each counter has correct value
        postgres_db._identity_map.clear()
        db_counters = [Counter.get(c.id) for c in counters]

        assert db_counters[0].value == 10
        assert db_counters[1].value == 20
        assert db_counters[2].value == 30

    def test_postgres_bulk_update_preserves_unmodified_fields(self, postgres_db):
        """Test that bulk_update preserves fields not in the update list."""

        class Settings(Model):
            key = CharField(max_length=100)
            value = CharField(max_length=200)
            enabled = BooleanField()

        postgres_db.register(Settings)

        settings = Settings.bulk_create(
            [
                Settings(key="setting1", value="value1", enabled=True),
                Settings(key="setting2", value="value2", enabled=False),
            ]
        )

        # Only update 'value' field
        settings[0].value = "new_value1"
        settings[1].value = "new_value2"

        Settings.bulk_update(settings, fields=["value"])

        # Verify 'enabled' field is preserved
        postgres_db._identity_map.clear()
        s1 = Settings.get(settings[0].id)
        s2 = Settings.get(settings[1].id)

        assert s1.value == "new_value1"
        assert s1.enabled is True  # Preserved
        assert s2.value == "new_value2"
        assert s2.enabled is False  # Preserved
