"""
Comprehensive test suite for BooleanField.

Tests cover:
- Type validation (accepts bool, rejects other types)
- Database storage and retrieval
- Nullable vs non-nullable fields
- Default values
- Filter operations
- Comparison operators
- SQLite and PostgreSQL compatibility (0/1 vs native boolean)
- Edge cases and error handling
"""

import pytest
from atomsql import Database, Model
from atomsql.orm.fields import StringField, BooleanField, IntegerField
from atomsql.orm.query import Q


# --- Test Models ---
class Feature(Model):
    """Model with nullable boolean field."""

    name = StringField()
    is_enabled = BooleanField(nullable=True)
    priority = IntegerField(default=1)


class Setting(Model):
    """Model with non-nullable boolean field."""

    key = StringField()
    value = BooleanField(nullable=False)


class Configuration(Model):
    """Model with boolean field with defaults."""

    name = StringField()
    is_active = BooleanField(default=True, nullable=False)
    is_public = BooleanField(default=False, nullable=True)


@pytest.fixture
def db():
    """Provides a fresh in-memory database."""
    database = Database("sqlite:///:memory:")
    yield database
    database.close()


@pytest.fixture
def feature_db(db):
    """Registers Feature model."""
    db.register(Feature)
    return db


@pytest.fixture
def setting_db(db):
    """Registers Setting model."""
    db.register(Setting)
    return db


@pytest.fixture
def config_db(db):
    """Registers Configuration model."""
    db.register(Configuration)
    return db


# ============================================================================
# Type Validation Tests
# ============================================================================


class TestBooleanFieldValidation:
    """Test suite for BooleanField type validation."""

    def test_accepts_true_value(self, feature_db):
        """BooleanField should accept True."""
        feature = Feature(name="Dark Mode", is_enabled=True)
        assert feature.is_enabled is True
        assert isinstance(feature.is_enabled, bool)

    def test_accepts_false_value(self, feature_db):
        """BooleanField should accept False."""
        feature = Feature(name="Beta Features", is_enabled=False)
        assert feature.is_enabled is False
        assert isinstance(feature.is_enabled, bool)

    def test_accepts_none_when_nullable(self, feature_db):
        """BooleanField should accept None when nullable=True."""
        feature = Feature(name="Optional Feature", is_enabled=None)
        assert feature.is_enabled is None

    def test_rejects_none_when_not_nullable(self, setting_db):
        """BooleanField should reject None when nullable=False."""
        with pytest.raises(ValueError, match="cannot be None"):
            Setting(key="required_setting", value=None)

    def test_rejects_integer_values(self, feature_db):
        """BooleanField should reject integer values."""
        with pytest.raises(ValueError, match="expected a bool"):
            Feature(name="Invalid", is_enabled=1)

        with pytest.raises(ValueError, match="expected a bool"):
            Feature(name="Invalid", is_enabled=0)

    def test_rejects_string_values(self, feature_db):
        """BooleanField should reject string values."""
        with pytest.raises(ValueError, match="expected a bool"):
            Feature(name="Invalid", is_enabled="true")

        with pytest.raises(ValueError, match="expected a bool"):
            Feature(name="Invalid", is_enabled="false")

    def test_rejects_list_values(self, feature_db):
        """BooleanField should reject list values."""
        with pytest.raises(ValueError, match="expected a bool"):
            Feature(name="Invalid", is_enabled=[True])

    def test_rejects_dict_values(self, feature_db):
        """BooleanField should reject dict values."""
        with pytest.raises(ValueError, match="expected a bool"):
            Feature(name="Invalid", is_enabled={"enabled": True})

    def test_rejects_float_values(self, feature_db):
        """BooleanField should reject float values."""
        with pytest.raises(ValueError, match="expected a bool"):
            Feature(name="Invalid", is_enabled=1.0)

        with pytest.raises(ValueError, match="expected a bool"):
            Feature(name="Invalid", is_enabled=0.0)


# ============================================================================
# Database Storage and Retrieval Tests
# ============================================================================


class TestBooleanFieldStorage:
    """Test suite for BooleanField database storage."""

    def test_save_and_retrieve_true(self, feature_db):
        """Save and retrieve True value."""
        feature = Feature(name="Dark Mode", is_enabled=True)
        feature.save(feature_db)
        feature_db.commit()

        retrieved = Feature.filter(name="Dark Mode").first()
        assert retrieved is not None
        assert retrieved.is_enabled is True
        assert isinstance(retrieved.is_enabled, bool)

    def test_save_and_retrieve_false(self, feature_db):
        """Save and retrieve False value."""
        feature = Feature(name="Beta Access", is_enabled=False)
        feature.save(feature_db)
        feature_db.commit()

        retrieved = Feature.filter(name="Beta Access").first()
        assert retrieved is not None
        assert retrieved.is_enabled is False
        assert isinstance(retrieved.is_enabled, bool)

    def test_save_and_retrieve_none(self, feature_db):
        """Save and retrieve None value."""
        feature = Feature(name="Undecided", is_enabled=None)
        feature.save(feature_db)
        feature_db.commit()

        retrieved = Feature.filter(name="Undecided").first()
        assert retrieved is not None
        assert retrieved.is_enabled is None

    def test_multiple_features_with_different_values(self, feature_db):
        """Save multiple records with different boolean values."""
        features = [
            Feature(name="Feature A", is_enabled=True),
            Feature(name="Feature B", is_enabled=False),
            Feature(name="Feature C", is_enabled=True),
            Feature(name="Feature D", is_enabled=None),
        ]

        for f in features:
            f.save(feature_db)
        feature_db.commit()

        all_features = list(Feature.all())
        assert len(all_features) == 4

        feature_a = Feature.filter(name="Feature A").first()
        feature_b = Feature.filter(name="Feature B").first()
        feature_c = Feature.filter(name="Feature C").first()
        feature_d = Feature.filter(name="Feature D").first()

        assert feature_a.is_enabled is True
        assert feature_b.is_enabled is False
        assert feature_c.is_enabled is True
        assert feature_d.is_enabled is None

    def test_update_boolean_value(self, feature_db):
        """Update a boolean field value."""
        feature = Feature(name="Toggleable", is_enabled=False)
        feature.save(feature_db)
        feature_db.commit()

        # Update to True
        feature.is_enabled = True
        feature.save(feature_db)
        feature_db.commit()

        retrieved = Feature.filter(name="Toggleable").first()
        assert retrieved.is_enabled is True

        # Update to None
        feature.is_enabled = None
        feature.save(feature_db)
        feature_db.commit()

        retrieved = Feature.filter(name="Toggleable").first()
        assert retrieved.is_enabled is None

    def test_from_db_value_conversion(self, feature_db):
        """Test from_db_value handles SQLite integers (0/1)."""
        from atomsql.orm.fields import BooleanField

        field = BooleanField()
        field.name = "test_field"

        # SQLite stores as 0/1
        assert field.from_db_value(0) is False
        assert field.from_db_value(1) is True

        # Direct boolean values
        assert field.from_db_value(False) is False
        assert field.from_db_value(True) is True

        # None value
        assert field.from_db_value(None) is None


# ============================================================================
# Default Values Tests
# ============================================================================


class TestBooleanFieldDefaults:
    """Test suite for BooleanField default values."""

    def test_default_true_value(self, config_db):
        """BooleanField with default=True should use True when not specified."""
        config = Configuration(name="Auto Config")
        config.save(config_db)
        config_db.commit()

        retrieved = Configuration.filter(name="Auto Config").first()
        assert retrieved.is_active is True

    def test_default_false_value(self, config_db):
        """BooleanField with default=False should use False when not specified."""
        config = Configuration(name="Private Config")
        config.save(config_db)
        config_db.commit()

        retrieved = Configuration.filter(name="Private Config").first()
        assert retrieved.is_public is False

    def test_explicit_value_overrides_default(self, config_db):
        """Explicit value should override default."""
        config = Configuration(name="Custom", is_active=False, is_public=True)
        config.save(config_db)
        config_db.commit()

        retrieved = Configuration.filter(name="Custom").first()
        assert retrieved.is_active is False  # Overrode default True
        assert retrieved.is_public is True  # Overrode default False

    def test_default_none_when_nullable(self, feature_db):
        """BooleanField with no default and nullable=True should default to None."""
        feature = Feature(name="No Default")
        assert feature.is_enabled is None


# ============================================================================
# Filter Operations Tests
# ============================================================================


class TestBooleanFieldFilters:
    """Test suite for filtering with BooleanField."""

    def test_filter_by_true(self, feature_db):
        """Filter records where boolean field is True."""
        Feature(name="Enabled 1", is_enabled=True).save(feature_db)
        Feature(name="Disabled", is_enabled=False).save(feature_db)
        Feature(name="Enabled 2", is_enabled=True).save(feature_db)
        feature_db.commit()

        enabled = list(Feature.filter(is_enabled=True))
        assert len(enabled) == 2
        assert all(f.is_enabled is True for f in enabled)

    def test_filter_by_false(self, feature_db):
        """Filter records where boolean field is False."""
        Feature(name="Enabled", is_enabled=True).save(feature_db)
        Feature(name="Disabled 1", is_enabled=False).save(feature_db)
        Feature(name="Disabled 2", is_enabled=False).save(feature_db)
        feature_db.commit()

        disabled = list(Feature.filter(is_enabled=False))
        assert len(disabled) == 2
        assert all(f.is_enabled is False for f in disabled)

    def test_filter_by_none(self, feature_db):
        """Filter records where boolean field is None."""
        Feature(name="Enabled", is_enabled=True).save(feature_db)
        Feature(name="Null 1", is_enabled=None).save(feature_db)
        Feature(name="Null 2", is_enabled=None).save(feature_db)
        feature_db.commit()

        # Note: Filtering by None with = doesn't work in SQL (need IS NULL)
        # This documents current behavior - may need future enhancement
        all_features = list(Feature.all())
        null_features = [f for f in all_features if f.is_enabled is None]
        assert len(null_features) == 2
        assert all(f.is_enabled is None for f in null_features)

    def test_exclude_true(self, feature_db):
        """Exclude records where boolean field is True."""
        Feature(name="Enabled", is_enabled=True).save(feature_db)
        Feature(name="Disabled", is_enabled=False).save(feature_db)
        Feature(name="Null", is_enabled=None).save(feature_db)
        feature_db.commit()

        # Exclude True should return False values (NULL behavior may vary)
        not_enabled = list(Feature.exclude(is_enabled=True))
        # At minimum should get the False value
        assert len(not_enabled) >= 1
        assert all(f.is_enabled is not True for f in not_enabled)

    def test_exclude_false(self, feature_db):
        """Exclude records where boolean field is False."""
        Feature(name="Enabled", is_enabled=True).save(feature_db)
        Feature(name="Disabled", is_enabled=False).save(feature_db)
        Feature(name="Null", is_enabled=None).save(feature_db)
        feature_db.commit()

        # Exclude False should return True values (NULL behavior may vary)
        not_disabled = list(Feature.exclude(is_enabled=False))
        # At minimum should get the True value
        assert len(not_disabled) >= 1
        assert all(f.is_enabled is not False for f in not_disabled)

    def test_chained_filters_with_boolean(self, feature_db):
        """Chain multiple filters including boolean fields."""
        Feature(name="Alpha", is_enabled=True, priority=1).save(feature_db)
        Feature(name="Beta", is_enabled=True, priority=2).save(feature_db)
        Feature(name="Gamma", is_enabled=False, priority=1).save(feature_db)
        feature_db.commit()

        results = list(Feature.filter(is_enabled=True).filter(priority=1))
        assert len(results) == 1
        assert results[0].name == "Alpha"

    def test_filter_with_q_objects(self, feature_db):
        """Filter using Q objects with boolean fields."""

        Feature(name="High Priority Enabled", is_enabled=True, priority=5).save(
            feature_db
        )
        Feature(name="Low Priority Enabled", is_enabled=True, priority=1).save(
            feature_db
        )
        Feature(name="High Priority Disabled", is_enabled=False, priority=5).save(
            feature_db
        )
        feature_db.commit()

        # Enabled OR high priority
        results = list(Feature.filter(Q(is_enabled=True) | Q(priority=5)))
        assert len(results) == 3

    def test_order_by_boolean_field(self, feature_db):
        """Order records by boolean field."""
        Feature(name="C", is_enabled=True).save(feature_db)
        Feature(name="A", is_enabled=False).save(feature_db)
        Feature(name="B", is_enabled=True).save(feature_db)
        feature_db.commit()

        # Order by is_enabled (False=0 comes before True=1 in SQLite)
        ordered = list(Feature.all().order_by("is_enabled"))
        assert len(ordered) == 3
        # First should be False, next two should be True
        assert ordered[0].is_enabled is False


# ============================================================================
# Comparison Operators Tests
# ============================================================================


class TestBooleanFieldOperators:
    """Test suite for BooleanField comparison operators."""

    def test_equality_operator(self, feature_db):
        """Test == operator with boolean fields."""
        Feature(name="Enabled", is_enabled=True).save(feature_db)
        Feature(name="Disabled", is_enabled=False).save(feature_db)
        feature_db.commit()

        # Using field comparison operator
        results = list(
            Feature.filter(Feature.is_enabled == 1)
        )  # Use 1 instead of True for explicit comparison
        assert len(results) == 1
        assert results[0].name == "Enabled"

    def test_inequality_operator(self, feature_db):
        """Test != operator with boolean fields."""
        Feature(name="Enabled", is_enabled=True).save(feature_db)
        Feature(name="Disabled", is_enabled=False).save(feature_db)
        feature_db.commit()

        results = list(
            Feature.filter(Feature.is_enabled == 0)
        )  # Use 0 instead of False for explicit comparison
        assert len(results) == 1
        assert results[0].name == "Disabled"

    def test_boolean_in_complex_expression(self, feature_db):
        """Test boolean field in complex query expressions."""
        Feature(name="A", is_enabled=True, priority=1).save(feature_db)
        Feature(name="B", is_enabled=True, priority=2).save(feature_db)
        Feature(name="C", is_enabled=False, priority=1).save(feature_db)
        feature_db.commit()

        # Enabled AND priority > 1
        results = list(Feature.filter(is_enabled=True).filter(priority__gt=1))
        assert len(results) == 1
        assert results[0].name == "B"


# ============================================================================
# Edge Cases and Error Handling
# ============================================================================


class TestBooleanFieldEdgeCases:
    """Test suite for edge cases and error conditions."""

    def test_empty_table_filter(self, feature_db):
        """Filter boolean field on empty table."""
        results = list(Feature.filter(is_enabled=True))
        assert len(results) == 0

    def test_all_true_values(self, feature_db):
        """Table with all True values."""
        for i in range(5):
            Feature(name=f"Feature {i}", is_enabled=True).save(feature_db)
        feature_db.commit()

        all_enabled = list(Feature.filter(is_enabled=True))
        assert len(all_enabled) == 5

        any_disabled = list(Feature.filter(is_enabled=False))
        assert len(any_disabled) == 0

    def test_all_false_values(self, feature_db):
        """Table with all False values."""
        for i in range(5):
            Feature(name=f"Feature {i}", is_enabled=False).save(feature_db)
        feature_db.commit()

        all_disabled = list(Feature.filter(is_enabled=False))
        assert len(all_disabled) == 5

        any_enabled = list(Feature.filter(is_enabled=True))
        assert len(any_enabled) == 0

    def test_all_none_values(self, feature_db):
        """Table with all None values."""
        for i in range(5):
            Feature(name=f"Feature {i}", is_enabled=None).save(feature_db)
        feature_db.commit()

        # Get all and verify they're None (filtering by None requires IS NULL)
        all_features = list(Feature.all())
        assert len(all_features) == 5
        assert all(f.is_enabled is None for f in all_features)

    def test_rapid_toggle(self, feature_db):
        """Rapidly toggle boolean value."""
        feature = Feature(name="Toggle", is_enabled=False)
        feature.save(feature_db)
        feature_db.commit()

        current_value = False
        for i in range(10):
            current_value = not current_value
            feature.is_enabled = current_value
            feature.save(feature_db)
            feature_db.commit()

            retrieved = Feature.filter(name="Toggle").first()
            assert retrieved.is_enabled == current_value

    def test_field_name_with_bool_in_name(self, db):
        """BooleanField with 'bool' in the field name."""

        class TestModel(Model):
            bool_flag = BooleanField()
            is_bool_active = BooleanField()

        db.register(TestModel)

        obj = TestModel(bool_flag=True, is_bool_active=False)
        obj.save(db)
        db.commit()

        retrieved = TestModel.all().first()
        assert retrieved.bool_flag is True
        assert retrieved.is_bool_active is False

    def test_multiple_boolean_fields(self, db):
        """Model with multiple boolean fields."""

        class Permission(Model):
            name = StringField()
            can_read = BooleanField(default=True)
            can_write = BooleanField(default=False)
            can_delete = BooleanField(default=False)
            is_admin = BooleanField(default=False)

        db.register(Permission)

        # Create with mixed values
        perm = Permission(
            name="Editor",
            can_read=True,
            can_write=True,
            can_delete=False,
            is_admin=False,
        )
        perm.save(db)
        db.commit()

        retrieved = Permission.filter(name="Editor").first()
        assert retrieved.can_read is True
        assert retrieved.can_write is True
        assert retrieved.can_delete is False
        assert retrieved.is_admin is False

    def test_boolean_with_unique_constraint(self, db):
        """BooleanField with unique=True constraint."""

        class UniqueFlag(Model):
            flag = BooleanField(unique=True, nullable=False)

        db.register(UniqueFlag)

        # First True should work
        flag1 = UniqueFlag(flag=True)
        flag1.save(db)
        db.commit()

        # First False should work
        flag2 = UniqueFlag(flag=False)
        flag2.save(db)
        db.commit()

        # Can only have two records (one True, one False)
        all_flags = list(UniqueFlag.all())
        assert len(all_flags) == 2


# ============================================================================
# Count and Aggregation Tests
# ============================================================================


class TestBooleanFieldAggregations:
    """Test suite for aggregations with BooleanField."""

    def test_count_true_values(self, feature_db):
        """Count records with True value."""
        Feature(name="A", is_enabled=True).save(feature_db)
        Feature(name="B", is_enabled=False).save(feature_db)
        Feature(name="C", is_enabled=True).save(feature_db)
        Feature(name="D", is_enabled=True).save(feature_db)
        feature_db.commit()

        count = Feature.filter(is_enabled=True).count()
        assert count == 3

    def test_count_false_values(self, feature_db):
        """Count records with False value."""
        Feature(name="A", is_enabled=True).save(feature_db)
        Feature(name="B", is_enabled=False).save(feature_db)
        Feature(name="C", is_enabled=False).save(feature_db)
        feature_db.commit()

        count = Feature.filter(is_enabled=False).count()
        assert count == 2

    def test_count_none_values(self, feature_db):
        """Count records with None value."""
        Feature(name="A", is_enabled=None).save(feature_db)
        Feature(name="B", is_enabled=True).save(feature_db)
        Feature(name="C", is_enabled=None).save(feature_db)
        feature_db.commit()

        # Count all and filter in Python (filtering NULL requires IS NULL syntax)
        all_features = list(Feature.all())
        none_count = sum(1 for f in all_features if f.is_enabled is None)
        assert none_count == 2

    def test_exists_with_boolean_filter(self, feature_db):
        """Test exists() with boolean filter."""
        Feature(name="Test", is_enabled=True).save(feature_db)
        feature_db.commit()

        assert Feature.filter(is_enabled=True).exists() is True
        assert Feature.filter(is_enabled=False).exists() is False

    def test_first_and_last_with_boolean(self, feature_db):
        """Test first() and last() with boolean fields."""
        Feature(name="First", is_enabled=True, priority=1).save(feature_db)
        Feature(name="Middle", is_enabled=False, priority=2).save(feature_db)
        Feature(name="Last", is_enabled=True, priority=3).save(feature_db)
        feature_db.commit()

        first_enabled = Feature.filter(is_enabled=True).order_by("priority").first()
        assert first_enabled.name == "First"

        last_enabled = Feature.filter(is_enabled=True).order_by("priority").last()
        assert last_enabled.name == "Last"


# ============================================================================
# Integration Tests
# ============================================================================


class TestBooleanFieldIntegration:
    """Integration tests for BooleanField with other ORM features."""

    def test_boolean_in_transaction(self, feature_db):
        """Test boolean field operations within a transaction."""
        with feature_db.transaction():
            feature = Feature(name="Transactional", is_enabled=True)
            feature.save(feature_db)
            feature_db.commit()

            # Verify within transaction
            retrieved = Feature.filter(name="Transactional").first()
            assert retrieved.is_enabled is True

        # Verify after transaction
        final = Feature.filter(name="Transactional").first()
        assert final.is_enabled is True

    def test_boolean_field_with_relationships(self, db):
        """Test BooleanField in models with relationships (if supported)."""

        class User(Model):
            name = StringField()
            is_active = BooleanField(default=True)

        class Post(Model):
            title = StringField()
            is_published = BooleanField(default=False)

        db.register(User)
        db.register(Post)

        user = User(name="Alice", is_active=True)
        user.save(db)

        post = Post(title="First Post", is_published=False)
        post.save(db)
        db.commit()

        # Verify both models
        retrieved_user = User.filter(name="Alice").first()
        retrieved_post = Post.filter(title="First Post").first()

        assert retrieved_user.is_active is True
        assert retrieved_post.is_published is False

    def test_boolean_with_identity_map(self, feature_db):
        """Test that identity map handles boolean fields correctly."""
        feature = Feature(name="Cached", is_enabled=True)
        feature.save(feature_db)
        feature_db.commit()

        # Fetch twice to test identity map
        first_fetch = Feature.filter(name="Cached").first()
        second_fetch = Feature.filter(name="Cached").first()

        # Should be same instance from identity map
        assert first_fetch.is_enabled is True
        assert second_fetch.is_enabled is True

        # Modify one
        first_fetch.is_enabled = False

        # Should affect the other (same instance)
        assert second_fetch.is_enabled is False
