"""Comprehensive test suite for JSONField."""

import pytest
from atomsql import Database, Model, StringField, IntegerField
from atomsql.orm.fields import JSONField


# Test Models
class UserProfile(Model):
    """Model with JSONField for testing user profile data."""

    username = StringField(nullable=False)
    metadata = JSONField()


class Configuration(Model):
    """Model with various JSONField configurations."""

    name = StringField(nullable=False)
    settings = JSONField(nullable=False)
    preferences = JSONField(nullable=True)
    defaults = JSONField(default={})


class Document(Model):
    """Model for testing complex JSON structures."""

    title = StringField()
    data = JSONField(nullable=False)
    tags = JSONField(nullable=True, default=None)


class Activity(Model):
    """Model for testing JSON with various data types."""

    user_id = IntegerField()
    event_data = JSONField()
    context = JSONField(nullable=True)


@pytest.fixture
def db():
    """Create an in-memory SQLite database."""
    database = Database("sqlite://:memory:")
    database.create_all()
    return database


# ============================================================================
# Tests for JSONField - Basic Functionality
# ============================================================================


def test_jsonfield_accepts_dict(db):
    """Test JSONField accepts a dictionary."""
    profile = UserProfile.create(
        username="alice", metadata={"age": 30, "city": "New York"}
    )

    assert profile.metadata == {"age": 30, "city": "New York"}
    assert isinstance(profile.metadata, dict)


def test_jsonfield_accepts_list(db):
    """Test JSONField accepts a list."""
    doc = Document.create(title="Test", data=[1, 2, 3, 4, 5])

    assert doc.data == [1, 2, 3, 4, 5]
    assert isinstance(doc.data, list)


def test_jsonfield_accepts_string(db):
    """Test JSONField accepts a JSON-serializable string."""
    profile = UserProfile.create(username="bob", metadata="simple string")

    assert profile.metadata == "simple string"
    assert isinstance(profile.metadata, str)


def test_jsonfield_accepts_number(db):
    """Test JSONField accepts numeric values."""
    profile = UserProfile.create(username="charlie", metadata=42)

    assert profile.metadata == 42
    assert isinstance(profile.metadata, int)


def test_jsonfield_accepts_boolean(db):
    """Test JSONField accepts boolean values."""
    profile = UserProfile.create(username="dave", metadata=True)

    assert profile.metadata is True
    assert isinstance(profile.metadata, bool)


def test_jsonfield_accepts_null(db):
    """Test JSONField accepts None when nullable."""
    profile = UserProfile.create(username="eve", metadata=None)

    assert profile.metadata is None


def test_jsonfield_accepts_nested_structure(db):
    """Test JSONField accepts nested JSON structures."""
    nested_data = {
        "user": {
            "name": "Alice",
            "contacts": {
                "email": "alice@example.com",
                "phone": ["123-456-7890", "098-765-4321"],
            },
        },
        "settings": {"notifications": True, "theme": "dark"},
    }
    doc = Document.create(title="Nested", data=nested_data)

    assert doc.data == nested_data
    assert doc.data["user"]["contacts"]["email"] == "alice@example.com"
    assert len(doc.data["user"]["contacts"]["phone"]) == 2


def test_jsonfield_accepts_empty_dict(db):
    """Test JSONField accepts an empty dictionary."""
    profile = UserProfile.create(username="frank", metadata={})

    assert profile.metadata == {}
    assert isinstance(profile.metadata, dict)


def test_jsonfield_accepts_empty_list(db):
    """Test JSONField accepts an empty list."""
    doc = Document.create(title="Empty", data=[])

    assert doc.data == []
    assert isinstance(doc.data, list)


# ============================================================================
# Tests for JSONField - Type Validation
# ============================================================================


def test_jsonfield_rejects_non_serializable_object(db):
    """Test JSONField rejects non-JSON-serializable objects."""

    class CustomClass:
        pass

    with pytest.raises(ValueError, match="expected a JSON-serializable value"):
        UserProfile.create(username="invalid", metadata=CustomClass())


def test_jsonfield_rejects_function(db):
    """Test JSONField rejects function objects."""

    def sample_function():
        pass

    with pytest.raises(ValueError, match="expected a JSON-serializable value"):
        UserProfile.create(username="invalid", metadata=sample_function)


def test_jsonfield_rejects_complex_types(db):
    """Test JSONField rejects complex number types."""
    with pytest.raises(ValueError, match="expected a JSON-serializable value"):
        UserProfile.create(username="invalid", metadata=complex(1, 2))


# ============================================================================
# Tests for JSONField - Persistence and Retrieval
# ============================================================================


def test_jsonfield_persists_and_retrieves_dict(db):
    """Test JSONField correctly persists and retrieves dictionary data."""
    data = {"name": "Alice", "age": 30, "active": True}
    UserProfile.create(username="alice", metadata=data)

    # Retrieve from database
    retrieved = UserProfile.objects().filter(username="alice")[0]

    assert retrieved.metadata == data
    assert isinstance(retrieved.metadata, dict)


def test_jsonfield_persists_and_retrieves_list(db):
    """Test JSONField correctly persists and retrieves list data."""
    data = [1, "two", 3.0, True, None]
    Document.create(title="List", data=data)

    retrieved = Document.objects().filter(title="List")[0]

    assert retrieved.data == data
    assert isinstance(retrieved.data, list)


def test_jsonfield_persists_and_retrieves_nested_structure(db):
    """Test JSONField correctly persists and retrieves nested structures."""
    data = {
        "level1": {
            "level2": {
                "level3": {"value": "deep", "items": [1, 2, 3]},
                "list": ["a", "b", "c"],
            }
        }
    }
    Document.create(title="Nested", data=data)

    retrieved = Document.objects().filter(title="Nested")[0]

    assert retrieved.data == data
    assert retrieved.data["level1"]["level2"]["level3"]["value"] == "deep"


def test_jsonfield_preserves_data_types(db):
    """Test JSONField preserves various JSON data types."""
    data = {
        "string": "text",
        "integer": 42,
        "float": 3.14,
        "boolean": True,
        "null": None,
        "list": [1, 2, 3],
        "nested_dict": {"key": "value"},
    }
    UserProfile.create(username="types", metadata=data)

    retrieved = UserProfile.objects().filter(username="types")[0]

    assert retrieved.metadata["string"] == "text"
    assert retrieved.metadata["integer"] == 42
    assert retrieved.metadata["float"] == 3.14
    assert retrieved.metadata["boolean"] is True
    assert retrieved.metadata["null"] is None
    assert retrieved.metadata["list"] == [1, 2, 3]
    assert retrieved.metadata["nested_dict"] == {"key": "value"}


# ============================================================================
# Tests for JSONField - Update Operations
# ============================================================================


def test_jsonfield_can_be_updated(db):
    """Test JSONField can be updated with new values."""
    profile = UserProfile.create(username="alice", metadata={"age": 30})

    # Update the field
    profile.metadata = {"age": 31, "city": "Boston"}
    profile.save()

    # Retrieve and verify
    retrieved = UserProfile.objects().filter(username="alice")[0]
    assert retrieved.metadata == {"age": 31, "city": "Boston"}


def test_jsonfield_update_from_dict_to_list(db):
    """Test JSONField can be updated from dict to list."""
    profile = UserProfile.create(username="bob", metadata={"key": "value"})

    profile.metadata = [1, 2, 3]
    profile.save()

    retrieved = UserProfile.objects().filter(username="bob")[0]
    assert retrieved.metadata == [1, 2, 3]
    assert isinstance(retrieved.metadata, list)


def test_jsonfield_update_to_null(db):
    """Test JSONField can be updated to None when nullable."""
    profile = UserProfile.create(username="charlie", metadata={"key": "value"})

    profile.metadata = None
    profile.save()

    retrieved = UserProfile.objects().filter(username="charlie")[0]
    assert retrieved.metadata is None


def test_jsonfield_update_complex_structure(db):
    """Test updating complex nested JSON structures."""
    initial_data = {"users": [{"id": 1, "name": "Alice"}]}
    doc = Document.create(title="Users", data=initial_data)

    updated_data = {
        "users": [
            {"id": 1, "name": "Alice", "email": "alice@example.com"},
            {"id": 2, "name": "Bob"},
        ]
    }
    doc.data = updated_data
    doc.save()

    retrieved = Document.objects().filter(title="Users")[0]
    assert len(retrieved.data["users"]) == 2
    assert retrieved.data["users"][0]["email"] == "alice@example.com"


# ============================================================================
# Tests for JSONField - Nullable Behavior
# ============================================================================


def test_jsonfield_nullable_true_accepts_none(db):
    """Test JSONField with nullable=True accepts None."""
    config = Configuration.create(
        name="test", settings={"key": "value"}, preferences=None
    )

    assert config.preferences is None


def test_jsonfield_nullable_false_rejects_none(db):
    """Test JSONField with nullable=False rejects None."""
    with pytest.raises(ValueError, match="cannot be None"):
        Configuration.create(name="test", settings=None, preferences={"key": "value"})


def test_jsonfield_nullable_update_to_none(db):
    """Test nullable JSONField can be updated to None."""
    config = Configuration.create(
        name="test", settings={"key": "value"}, preferences={"theme": "dark"}
    )

    config.preferences = None
    config.save()

    retrieved = Configuration.objects().filter(name="test")[0]
    assert retrieved.preferences is None


def test_jsonfield_non_nullable_cannot_update_to_none(db):
    """Test non-nullable JSONField cannot be updated to None."""
    config = Configuration.create(
        name="test", settings={"key": "value"}, preferences=None
    )

    with pytest.raises(ValueError, match="cannot be None"):
        config.settings = None


# ============================================================================
# Tests for JSONField - Default Values
# ============================================================================


def test_jsonfield_uses_default_dict(db):
    """Test JSONField uses default dictionary value."""
    config = Configuration(name="test", settings={"key": "value"})

    # defaults should use the default empty dict
    assert config.defaults == {}


def test_jsonfield_uses_default_none(db):
    """Test JSONField uses default None value."""
    doc = Document(title="Test", data={"key": "value"})

    assert doc.tags is None


def test_jsonfield_default_is_not_shared(db):
    """Test JSONField default values are not shared between instances."""
    # Create first instance with default
    config1 = Configuration(name="config1", settings={"key": "value"})
    config1.defaults = {"setting1": "A"}

    # Create second instance with default
    config2 = Configuration(name="config2", settings={"key": "value"})

    # Verify defaults are not shared
    assert config2.defaults == {}
    assert config1.defaults == {"setting1": "A"}


# ============================================================================
# Tests for JSONField - Special Cases
# ============================================================================


def test_jsonfield_with_unicode_characters(db):
    """Test JSONField handles Unicode characters correctly."""
    data = {
        "english": "Hello",
        "chinese": "你好",
        "arabic": "مرحبا",
        "emoji": "😀🎉",
    }
    UserProfile.create(username="multilang", metadata=data)

    retrieved = UserProfile.objects().filter(username="multilang")[0]
    assert retrieved.metadata["chinese"] == "你好"
    assert retrieved.metadata["arabic"] == "مرحبا"
    assert retrieved.metadata["emoji"] == "😀🎉"


def test_jsonfield_with_special_characters(db):
    """Test JSONField handles special characters correctly."""
    data = {"text": 'Special: " \\ \n \t \r', "quote": "It's working"}
    UserProfile.create(username="special", metadata=data)

    retrieved = UserProfile.objects().filter(username="special")[0]
    assert retrieved.metadata["text"] == 'Special: " \\ \n \t \r'
    assert retrieved.metadata["quote"] == "It's working"


def test_jsonfield_with_large_data(db):
    """Test JSONField handles large JSON data."""
    # Create a large list
    large_list = list(range(1000))
    Document.create(title="Large", data=large_list)

    retrieved = Document.objects().filter(title="Large")[0]
    assert len(retrieved.data) == 1000
    assert retrieved.data[0] == 0
    assert retrieved.data[999] == 999


def test_jsonfield_with_deeply_nested_structure(db):
    """Test JSONField handles deeply nested structures."""
    # Create a deeply nested structure
    nested = {"level": 1}
    current = nested
    for i in range(2, 11):
        current["child"] = {"level": i}
        current = current["child"]

    Document.create(title="Deep", data=nested)

    retrieved = Document.objects().filter(title="Deep")[0]
    assert retrieved.data["level"] == 1
    assert retrieved.data["child"]["child"]["child"]["level"] == 4


def test_jsonfield_empty_string_in_data(db):
    """Test JSONField handles empty strings within data."""
    data = {"empty": "", "name": "Alice", "blank": ""}
    UserProfile.create(username="empty", metadata=data)

    retrieved = UserProfile.objects().filter(username="empty")[0]
    assert retrieved.metadata["empty"] == ""
    assert retrieved.metadata["blank"] == ""


def test_jsonfield_numeric_keys_in_dict(db):
    """Test JSONField with numeric keys (converted to strings in JSON)."""
    # Note: JSON converts numeric keys to strings
    data = {1: "one", 2: "two", 3: "three"}
    UserProfile.create(username="numkeys", metadata=data)

    retrieved = UserProfile.objects().filter(username="numkeys")[0]
    # After JSON serialization/deserialization, numeric keys become strings
    assert "1" in str(retrieved.metadata) or 1 in retrieved.metadata


def test_jsonfield_mixed_list_types(db):
    """Test JSONField with mixed types in a list."""
    data = [1, "two", 3.5, True, None, {"key": "value"}, [1, 2, 3]]
    Document.create(title="Mixed", data=data)

    retrieved = Document.objects().filter(title="Mixed")[0]
    assert retrieved.data[0] == 1
    assert retrieved.data[1] == "two"
    assert retrieved.data[2] == 3.5
    assert retrieved.data[3] is True
    assert retrieved.data[4] is None
    assert retrieved.data[5] == {"key": "value"}
    assert retrieved.data[6] == [1, 2, 3]


# ============================================================================
# Tests for JSONField - Query Operations
# ============================================================================


def test_jsonfield_filter_equality(db):
    """Test filtering by JSONField equality (basic dict)."""
    data = {"status": "active", "priority": "high"}
    UserProfile.create(username="alice", metadata=data)
    UserProfile.create(username="bob", metadata={"status": "inactive"})

    # Note: Querying by exact JSON match may have database-specific behavior
    # This test demonstrates basic retrieval
    retrieved = UserProfile.objects().filter(username="alice")[0]
    assert retrieved.metadata == data


def test_jsonfield_multiple_records(db):
    """Test multiple records with different JSON data."""
    UserProfile.create(username="alice", metadata={"role": "admin"})
    UserProfile.create(username="bob", metadata={"role": "user"})
    UserProfile.create(username="charlie", metadata={"role": "moderator"})

    alice = UserProfile.objects().filter(username="alice")[0]
    bob = UserProfile.objects().filter(username="bob")[0]

    assert alice.metadata["role"] == "admin"
    assert bob.metadata["role"] == "user"


# ============================================================================
# Tests for JSONField - Edge Cases
# ============================================================================


def test_jsonfield_zero_values(db):
    """Test JSONField with zero values."""
    data = {"count": 0, "value": 0.0, "empty": False}
    UserProfile.create(username="zeros", metadata=data)

    retrieved = UserProfile.objects().filter(username="zeros")[0]
    assert retrieved.metadata["count"] == 0
    assert retrieved.metadata["value"] == 0.0
    assert retrieved.metadata["empty"] is False


def test_jsonfield_negative_numbers(db):
    """Test JSONField with negative numbers."""
    data = {"temperature": -15, "balance": -100.50}
    UserProfile.create(username="negative", metadata=data)

    retrieved = UserProfile.objects().filter(username="negative")[0]
    assert retrieved.metadata["temperature"] == -15
    assert retrieved.metadata["balance"] == -100.50


def test_jsonfield_scientific_notation(db):
    """Test JSONField with scientific notation numbers."""
    data = {"small": 1e-10, "large": 1e10}
    UserProfile.create(username="scientific", metadata=data)

    retrieved = UserProfile.objects().filter(username="scientific")[0]
    assert retrieved.metadata["small"] == 1e-10
    assert retrieved.metadata["large"] == 1e10


def test_jsonfield_duplicate_keys_last_wins(db):
    """Test JSONField behavior with duplicate keys (last value wins)."""
    # In Python dicts, last value wins for duplicate keys
    data = {"key": "first"}
    data["key"] = "second"
    UserProfile.create(username="duplicate", metadata=data)

    retrieved = UserProfile.objects().filter(username="duplicate")[0]
    assert retrieved.metadata["key"] == "second"


# ============================================================================
# Tests for JSONField - Database Interaction
# ============================================================================


def test_jsonfield_from_db_value_with_valid_json_string(db):
    """Test from_db_value method with valid JSON string."""
    field = JSONField()
    field.name = "test_field"

    result = field.from_db_value('{"key": "value"}')
    assert result == {"key": "value"}


def test_jsonfield_from_db_value_with_null(db):
    """Test from_db_value method with None."""
    field = JSONField()
    field.name = "test_field"

    result = field.from_db_value(None)
    assert result is None


def test_jsonfield_from_db_value_with_invalid_json(db):
    """Test from_db_value method with invalid JSON string."""
    field = JSONField()
    field.name = "test_field"

    with pytest.raises(ValueError, match="could not parse JSON from database"):
        field.from_db_value("not valid json {")


def test_jsonfield_from_db_value_with_already_parsed(db):
    """Test from_db_value method when value is already a dict/list."""
    field = JSONField()
    field.name = "test_field"

    # Some database drivers might return already-parsed JSON
    result = field.from_db_value({"key": "value"})
    assert result == {"key": "value"}

    result = field.from_db_value([1, 2, 3])
    assert result == [1, 2, 3]


# ============================================================================
# Tests for JSONField - Validation Method
# ============================================================================


def test_jsonfield_validate_type_with_valid_data(db):
    """Test validate_type method with valid JSON-serializable data."""
    field = JSONField()
    field.name = "test_field"

    # Should not raise any exception
    field.validate_type({"key": "value"})
    field.validate_type([1, 2, 3])
    field.validate_type("string")
    field.validate_type(123)
    field.validate_type(True)


def test_jsonfield_validate_type_with_none_nullable(db):
    """Test validate_type method with None on nullable field."""
    field = JSONField(nullable=True)
    field.name = "test_field"

    # Should not raise any exception
    field.validate_type(None)


def test_jsonfield_validate_type_with_invalid_data(db):
    """Test validate_type method with non-serializable data."""
    field = JSONField()
    field.name = "test_field"

    class CustomClass:
        pass

    with pytest.raises(ValueError, match="expected a JSON-serializable value"):
        field.validate_type(CustomClass())


# ============================================================================
# Tests for JSONField - Assignment and Retrieval
# ============================================================================


def test_jsonfield_direct_assignment(db):
    """Test direct assignment to JSONField before save."""
    profile = UserProfile(username="alice")
    profile.metadata = {"age": 30}

    assert profile.metadata == {"age": 30}


def test_jsonfield_assignment_after_create(db):
    """Test assignment to JSONField after create."""
    profile = UserProfile.create(username="alice", metadata={"age": 30})

    profile.metadata = {"age": 31, "city": "Boston"}
    assert profile.metadata == {"age": 31, "city": "Boston"}


def test_jsonfield_get_descriptor(db):
    """Test accessing JSONField from class returns the field itself."""
    field = UserProfile.metadata
    assert isinstance(field, JSONField)


# ============================================================================
# Tests for JSONField - Complex Real-World Scenarios
# ============================================================================


def test_jsonfield_user_preferences(db):
    """Test JSONField for user preferences scenario."""
    preferences = {
        "theme": "dark",
        "language": "en",
        "notifications": {
            "email": True,
            "push": False,
            "sms": True,
        },
        "privacy": {
            "profile_visible": True,
            "show_email": False,
        },
    }
    UserProfile.create(username="alice", metadata=preferences)

    retrieved = UserProfile.objects().filter(username="alice")[0]
    assert retrieved.metadata["theme"] == "dark"
    assert retrieved.metadata["notifications"]["email"] is True
    assert retrieved.metadata["privacy"]["show_email"] is False


def test_jsonfield_activity_log(db):
    """Test JSONField for activity log scenario."""
    event = {
        "timestamp": "2026-01-10T10:30:00Z",
        "action": "login",
        "ip_address": "192.168.1.100",
        "user_agent": "Mozilla/5.0",
        "metadata": {
            "success": True,
            "method": "oauth",
            "provider": "google",
        },
    }
    Activity.create(user_id=1, event_data=event, context=None)

    retrieved = Activity.objects().filter(user_id=1)[0]
    assert retrieved.event_data["action"] == "login"
    assert retrieved.event_data["metadata"]["provider"] == "google"


def test_jsonfield_api_response(db):
    """Test JSONField for storing API response scenario."""
    api_response = {
        "status": 200,
        "data": {
            "users": [
                {"id": 1, "name": "Alice", "email": "alice@example.com"},
                {"id": 2, "name": "Bob", "email": "bob@example.com"},
            ],
            "pagination": {"page": 1, "per_page": 10, "total": 2},
        },
        "headers": {"content-type": "application/json"},
    }
    Document.create(title="API Response", data=api_response)

    retrieved = Document.objects().filter(title="API Response")[0]
    assert retrieved.data["status"] == 200
    assert len(retrieved.data["data"]["users"]) == 2
    assert retrieved.data["data"]["pagination"]["total"] == 2
