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


class User(Model):
    """Test model for PostgreSQL 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 for PostgreSQL."""

    _table_name = "products"

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


@pytest.fixture(scope="module")
def db():
    """Create PostgreSQL test database connection."""
    # Check if PostgreSQL is available
    try:
        db_url = os.getenv(
            "TEST_POSTGRES_URL", "postgresql://postgres:password@localhost/test_atomsql"
        )
        database = Database(db_url)

        # Test connection and register models
        database.register(User)
        database.register(Product)

        yield database

        # Cleanup
        with database.transaction():
            database.backend.execute("DROP TABLE IF EXISTS users CASCADE")
            database.backend.execute("DROP TABLE IF EXISTS products CASCADE")

    except (psycopg.OperationalError, ImportError):
        pytest.skip("PostgreSQL not available for testing")


class TestQuerySetUpdatePostgreSQL:
    """Test QuerySet update() method with PostgreSQL-specific behavior."""

    def test_update_large_batch_postgres(self, db):
        """Test updating large number of records in PostgreSQL."""
        # Create large batch of test records
        users = []
        for i in range(1000):
            users.append(
                User.create(
                    name=f"User{i}",
                    age=20 + (i % 50),
                    active=i % 2 == 0,
                    email=f"user{i}@test.com",
                    db=db,
                )
            )

        # Update all active users (should be ~500)
        updated_count = User.filter(active=True, db=db).update(age=100)

        assert updated_count == 500

        # Verify updates
        active_users = list(User.filter(active=True, db=db))
        for user in active_users[:10]:  # Check first 10
            assert user.age == 100

    def test_update_with_postgres_specific_operators(self, db):
        """Test update with PostgreSQL-specific query operators."""
        # Create test records
        User.create(name="User1", age=25, active=True, email="user1@test.com", db=db)
        User.create(name="User2", age=35, active=True, email="user2@test.com", db=db)
        User.create(name="User3", age=45, active=False, email="user3@test.com", db=db)

        # Update using __in filter (PostgreSQL optimized)
        age_list = [25, 35]
        updated_count = User.filter(age__in=age_list, db=db).update(active=False)

        assert updated_count == 2

        # Verify updates
        user1 = User.get(name="User1", db=db)
        user2 = User.get(name="User2", db=db)
        assert user1.active is False
        assert user2.active is False

    def test_update_with_case_sensitive_strings_postgres(self, db):
        """Test update with case-sensitive string handling in PostgreSQL."""
        # Create test records
        User.create(
            name="TestUser", age=25, active=True, email="TEST@EXAMPLE.COM", db=db
        )

        # Update with case-sensitive match
        updated_count = User.filter(email="TEST@EXAMPLE.COM", db=db).update(
            email="test@example.com"
        )

        assert updated_count == 1

        # Verify case was preserved in update
        user = User.get(name="TestUser", db=db)
        assert user.email == "test@example.com"

    def test_update_concurrent_access_postgres(self, db):
        """Test update behavior with concurrent access (PostgreSQL locking)."""
        # Create test record
        User.create(
            name="ConcurrentTest", age=25, active=True, email="test@test.com", db=db
        )

        # Simulate concurrent updates within transaction
        with db.transaction():
            # First update
            updated_count1 = User.filter(name="ConcurrentTest", db=db).update(age=30)
            assert updated_count1 == 1

            # Second update on same record within same transaction
            updated_count2 = User.filter(name="ConcurrentTest", db=db).update(
                active=False
            )
            assert updated_count2 == 1

        # Verify both updates applied
        user = User.get(name="ConcurrentTest", db=db)
        assert user.age == 30
        assert user.active is False

    def test_update_with_complex_where_postgres(self, db):
        """Test update with complex WHERE clauses in PostgreSQL."""
        # Create test records
        for i in range(20):
            User.create(
                name=f"ComplexUser{i}",
                age=20 + i,
                active=i % 3 == 0,
                email=f"complex{i}@test.com",
                db=db,
            )

        # Complex filter: active users with age between 25 and 35
        updated_count = (
            User.filter(active=True, db=db)
            .filter(age__gte=25)
            .filter(age__lte=35)
            .update(email="updated@test.com")
        )

        # Should match users at indexes 6, 9, 12, 15 (ages 26, 29, 32, 35)
        # But only 6, 12 are active (i % 3 == 0)
        assert updated_count == 2

        # Verify specific updates
        updated_users = list(User.filter(email="updated@test.com", db=db))
        assert len(updated_users) == 2

    def test_update_with_null_handling_postgres(self, db):
        """Test update behavior with NULL values in PostgreSQL."""
        # Create test record
        User.create(name="NullTest", age=25, active=True, email="null@test.com", db=db)

        # Update to None/NULL (if field allows it)
        # Note: Our BoolField might not allow NULL, so test with optional field if available
        updated_count = User.filter(name="NullTest", db=db).update(email="")

        assert updated_count == 1

        user = User.get(name="NullTest", db=db)
        assert user.email == ""

    def test_update_transaction_isolation_postgres(self, db):
        """Test update behavior with PostgreSQL transaction isolation."""
        # Create test record
        User.create(
            name="IsolationTest", age=25, active=True, email="isolation@test.com", db=db
        )

        # Test transaction isolation
        with db.transaction():
            # Update within transaction
            updated_count = User.filter(name="IsolationTest", db=db).update(age=30)
            assert updated_count == 1

            # Verify update is visible within same transaction
            user = User.get(name="IsolationTest", db=db)
            assert user.age == 30

        # Verify update persists after transaction commit
        user = User.get(name="IsolationTest", db=db)
        assert user.age == 30

    def test_update_with_special_characters_postgres(self, db):
        """Test update with special characters in PostgreSQL."""
        # Create test record with special characters
        User.create(
            name="Special'User\"Test",
            age=25,
            active=True,
            email="special@test.com",
            db=db,
        )

        # Update with special characters
        updated_count = User.filter(name="Special'User\"Test", db=db).update(
            email="new'email\"@test.com"
        )

        assert updated_count == 1

        user = User.get(name="Special'User\"Test", db=db)
        assert user.email == "new'email\"@test.com"

    def test_update_performance_large_dataset_postgres(self, db):
        """Test update performance with large dataset in PostgreSQL."""
        import time

        # Create large dataset
        batch_size = 5000
        for i in range(batch_size):
            User.create(
                name=f"PerfUser{i}",
                age=20 + (i % 60),
                active=i % 2 == 0,
                email=f"perf{i}@test.com",
                db=db,
            )

        # Time bulk update
        start_time = time.time()
        updated_count = User.filter(active=True, db=db).update(age=999)
        bulk_time = time.time() - start_time

        expected_count = batch_size // 2
        assert updated_count == expected_count
        assert bulk_time < 5.0  # Should complete within 5 seconds

        # Verify updates
        updated_users = list(User.filter(age=999, db=db))
        assert len(updated_users) == expected_count

    def test_update_with_postgres_text_search(self, db):
        """Test update with text search patterns in PostgreSQL."""
        # Create test records
        User.create(
            name="John Doe", age=25, active=True, email="john.doe@test.com", db=db
        )
        User.create(
            name="Jane Doe", age=30, active=True, email="jane.doe@test.com", db=db
        )
        User.create(
            name="Bob Smith", age=35, active=True, email="bob.smith@test.com", db=db
        )

        # Update records with names containing "Doe"
        updated_count = User.filter(name__contains="Doe", db=db).update(active=False)

        assert updated_count == 2

        # Verify updates
        does = list(User.filter(active=False, db=db))
        assert len(does) == 2

        bob = User.get(name="Bob Smith", db=db)
        assert bob.active is True

    def test_update_with_range_queries_postgres(self, db):
        """Test update with range queries optimized for PostgreSQL."""
        # Create test records
        for i in range(50):
            User.create(
                name=f"RangeUser{i}",
                age=20 + i,
                active=True,
                email=f"range{i}@test.com",
                db=db,
            )

        # Update users in age range 30-40
        updated_count = (
            User.filter(age__gte=30, db=db)
            .filter(age__lte=40)
            .update(email="in_range@test.com")
        )

        assert updated_count == 11  # Ages 30-40 inclusive

        # Verify updates
        in_range_users = list(User.filter(email="in_range@test.com", db=db))
        assert len(in_range_users) == 11

    def test_update_rowcount_accuracy_postgres(self, db):
        """Test that PostgreSQL returns accurate rowcount for updates."""
        # Create varied test dataset
        for i in range(25):
            User.create(
                name=f"CountUser{i}",
                age=25 + (i % 10),
                active=i % 4 == 0,  # Every 4th user is active
                email=f"count{i}@test.com",
                db=db,
            )

        # Update active users (should be 7: indexes 0, 4, 8, 12, 16, 20, 24)
        updated_count = User.filter(active=True, db=db).update(age=100)
        assert updated_count == 7

        # Update users with specific age (age 25: indexes 0, 10, 20)
        updated_count = User.filter(age=25, db=db).update(active=False)
        # Only index 0 had age 25 but was updated to 100 in previous step
        # Index 10 has age 25 and active=False
        # Index 20 has age 25 but was updated to 100 in previous step
        updated_count = User.filter(age=25, db=db).update(active=False)
        assert updated_count == 1  # Only index 10
