"""Comprehensive test suite for Model.create() and Model.get_or_create() methods."""

import pytest
from datetime import datetime, date
from atomsql import Database, Model, StringField, IntegerField, FloatField
from atomsql.orm.fields import BooleanField, DateField, DateTimeField


# Test Models
class User(Model):
    username = StringField()
    email = StringField()
    age = IntegerField(default=0)
    is_active = BooleanField(default=True)


class Product(Model):
    name = StringField()
    sku = StringField(unique=True)
    price = FloatField(default=0.0)
    stock = IntegerField(default=0)


class Article(Model):
    title = StringField()
    slug = StringField(unique=True)
    content = StringField()
    published_date = DateField(nullable=True)
    created_at = DateTimeField(nullable=True)


class Tag(Model):
    name = StringField(unique=True)
    description = StringField(nullable=True)


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


# ============================================================================
# Tests for Model.create()
# ============================================================================


def test_create_basic(db):
    """Test basic create with all required fields."""
    user = User.create(username="john_doe", email="john@example.com")

    assert user.id is not None
    assert user.username == "john_doe"
    assert user.email == "john@example.com"
    assert user.age == 0  # default value
    assert user.is_active is True  # default value


def test_create_with_all_fields(db):
    """Test create with all fields including defaults."""
    user = User.create(
        username="jane_doe", email="jane@example.com", age=25, is_active=False
    )

    assert user.id is not None
    assert user.username == "jane_doe"
    assert user.email == "jane@example.com"
    assert user.age == 25
    assert user.is_active is False


def test_create_persists_to_database(db):
    """Test that create actually saves to the database."""
    user = User.create(username="alice", email="alice@example.com")

    # Retrieve from database
    retrieved = User.get(user.id)
    assert retrieved is not None
    assert retrieved.username == "alice"
    assert retrieved.email == "alice@example.com"


def test_create_multiple_instances(db):
    """Test creating multiple instances."""
    user1 = User.create(username="user1", email="user1@example.com")
    user2 = User.create(username="user2", email="user2@example.com")
    user3 = User.create(username="user3", email="user3@example.com")

    assert user1.id != user2.id
    assert user2.id != user3.id
    assert user1.id != user3.id

    # Verify all are in database
    all_users = User.all()
    assert len(all_users) == 3


def test_create_with_nullable_fields(db):
    """Test create with nullable fields set to None."""
    article = Article.create(
        title="Test Article",
        slug="test-article",
        content="Test content",
        published_date=None,
        created_at=None,
    )

    assert article.id is not None
    assert article.published_date is None
    assert article.created_at is None


def test_create_with_date_field(db):
    """Test create with DateField."""
    today = date.today()
    article = Article.create(
        title="Published Article",
        slug="published-article",
        content="Content",
        published_date=today,
    )

    assert article.published_date == today


def test_create_with_datetime_field(db):
    """Test create with DateTimeField."""
    now = datetime.now()
    article = Article.create(
        title="Timestamped Article",
        slug="timestamped-article",
        content="Content",
        created_at=now,
    )

    assert article.created_at == now


def test_create_with_decimal_field(db):
    """Test create with DecimalField."""
    product = Product.create(name="Laptop", sku="LAP-001", price=999.99, stock=10)

    assert product.price == 999.99
    assert product.stock == 10


def test_create_with_default_callable(db):
    """Test that default values are properly applied."""
    tag = Tag.create(name="python")

    assert tag.id is not None
    assert tag.name == "python"
    assert tag.description is None  # nullable field


def test_create_returns_saved_instance(db):
    """Test that create returns an instance with an ID."""
    user = User.create(username="testuser", email="test@example.com")

    assert isinstance(user, User)
    assert user.id is not None


def test_create_without_db_registration(db):
    """Test create without database registration raises error."""

    class UnregisteredModel(Model):
        name = StringField()

    with pytest.raises(RuntimeError) as exc_info:
        UnregisteredModel.create(name="test")

    assert "not registered to any database" in str(exc_info.value)


# ============================================================================
# Tests for Model.get_or_create()
# ============================================================================


def test_get_or_create_creates_new_instance(db):
    """Test get_or_create creates a new instance when it doesn't exist."""
    user, created = User.get_or_create(username="new_user", email="new@example.com")

    assert created is True
    assert user.id is not None
    assert user.username == "new_user"
    assert user.email == "new@example.com"


def test_get_or_create_returns_existing_instance(db):
    """Test get_or_create returns existing instance when found."""
    # Create initial user
    User.create(username="existing_user", email="existing@example.com", age=30)

    # Try to get or create with same username
    user, created = User.get_or_create(username="existing_user")

    assert created is False
    assert user.username == "existing_user"
    assert user.email == "existing@example.com"
    assert user.age == 30


def test_get_or_create_with_defaults_on_create(db):
    """Test get_or_create uses defaults when creating."""
    user, created = User.get_or_create(
        username="with_defaults", defaults={"email": "default@example.com", "age": 25}
    )

    assert created is True
    assert user.username == "with_defaults"
    assert user.email == "default@example.com"
    assert user.age == 25


def test_get_or_create_with_defaults_on_get(db):
    """Test get_or_create ignores defaults when getting existing."""
    # Create initial user
    User.create(username="user", email="original@example.com", age=20)

    # Try to get or create with different defaults
    user, created = User.get_or_create(
        username="user", defaults={"email": "new@example.com", "age": 30}
    )

    assert created is False
    assert user.email == "original@example.com"  # Original value kept
    assert user.age == 20  # Original value kept


def test_get_or_create_multiple_lookup_fields(db):
    """Test get_or_create with multiple lookup fields."""
    # Create user
    User.create(username="multi", email="multi@example.com", age=25)

    # Get by multiple fields
    user, created = User.get_or_create(username="multi", email="multi@example.com")

    assert created is False
    assert user.username == "multi"
    assert user.age == 25


def test_get_or_create_partial_match_creates_new(db):
    """Test get_or_create creates new when partial match doesn't exist."""
    # Create user with specific username and email
    User.create(username="partial", email="first@example.com")

    # Try to get or create with same username but different email
    user, created = User.get_or_create(username="partial", email="second@example.com")

    assert created is True  # Creates new because both fields don't match
    assert user.email == "second@example.com"


def test_get_or_create_returns_tuple(db):
    """Test get_or_create returns a tuple of (instance, created)."""
    result = User.get_or_create(username="tuple_test", email="tuple@example.com")

    assert isinstance(result, tuple)
    assert len(result) == 2
    assert isinstance(result[0], User)
    assert isinstance(result[1], bool)


def test_get_or_create_persists_to_database(db):
    """Test get_or_create persists new instances to database."""
    user, created = User.get_or_create(
        username="persist", defaults={"email": "persist@example.com"}
    )

    assert created is True

    # Retrieve from database
    retrieved = User.get(user.id)
    assert retrieved is not None
    assert retrieved.username == "persist"


def test_get_or_create_with_unique_field(db):
    """Test get_or_create with unique field constraint."""
    # Create tag with unique name
    Tag.create(name="python", description="Python programming")

    # Get or create with same name
    tag, created = Tag.get_or_create(name="python")

    assert created is False
    assert tag.name == "python"
    assert tag.description == "Python programming"


def test_get_or_create_with_all_field_types(db):
    """Test get_or_create with various field types."""
    today = date.today()
    now = datetime.now()

    article, created = Article.get_or_create(
        slug="comprehensive",
        defaults={
            "title": "Comprehensive Article",
            "content": "Full content",
            "published_date": today,
            "created_at": now,
        },
    )

    assert created is True
    assert article.title == "Comprehensive Article"
    assert article.published_date == today
    assert article.created_at == now


def test_get_or_create_empty_defaults(db):
    """Test get_or_create with empty defaults dict."""
    user, created = User.get_or_create(
        username="empty_defaults", email="empty@example.com", defaults={}
    )

    assert created is True
    assert user.username == "empty_defaults"


def test_get_or_create_no_defaults(db):
    """Test get_or_create without defaults parameter."""
    user, created = User.get_or_create(
        username="no_defaults", email="nodefaults@example.com"
    )

    assert created is True
    assert user.username == "no_defaults"
    assert user.email == "nodefaults@example.com"


def test_get_or_create_lookup_by_single_field(db):
    """Test get_or_create with single lookup field."""
    # Create tag
    Tag.create(name="python", description="Python programming")

    # Get or create by name only
    tag, created = Tag.get_or_create(name="python")

    assert created is False
    assert tag.description == "Python programming"


def test_get_or_create_creates_with_lookup_and_defaults(db):
    """Test get_or_create merges lookup fields and defaults."""
    tag, created = Tag.get_or_create(
        name="django", defaults={"description": "Django web framework"}
    )

    assert created is True
    assert tag.name == "django"
    assert tag.description == "Django web framework"


def test_get_or_create_without_db_registration(db):
    """Test get_or_create without database registration raises error."""

    class UnregisteredModel(Model):
        name = StringField()

    with pytest.raises(RuntimeError) as exc_info:
        UnregisteredModel.get_or_create(name="test")

    assert "not registered to any database" in str(exc_info.value)


def test_get_or_create_handles_race_condition(db):
    """Test get_or_create handles race conditions gracefully."""
    # This simulates what happens if two concurrent requests try to create
    # the same object. The transaction handling should prevent duplicates.
    user1, created1 = User.get_or_create(
        username="race_test", defaults={"email": "race@example.com"}
    )

    user2, created2 = User.get_or_create(
        username="race_test", defaults={"email": "race@example.com"}
    )

    assert user1.id == user2.id
    assert created1 is True
    assert created2 is False


def test_get_or_create_with_nullable_field_none(db):
    """Test get_or_create with nullable field set to None."""
    tag, created = Tag.get_or_create(
        name="nullable_test", defaults={"description": None}
    )

    assert created is True
    assert tag.description is None


def test_get_or_create_updates_nothing_on_get(db):
    """Test get_or_create doesn't update existing records."""
    # Create original
    original = Product.create(name="Original", sku="ORIG-001", price=100.0, stock=50)
    original_id = original.id

    # Get or create with different values in defaults
    product, created = Product.get_or_create(
        sku="ORIG-001", defaults={"name": "Updated Name", "price": 200.0, "stock": 100}
    )

    assert created is False
    assert product.id == original_id
    assert product.name == "Original"  # Not updated
    assert product.price == 100.0  # Not updated
    assert product.stock == 50  # Not updated


def test_get_or_create_multiple_consecutive_creates(db):
    """Test multiple consecutive get_or_create calls for different objects."""
    user1, created1 = User.get_or_create(
        username="user1", defaults={"email": "user1@example.com"}
    )
    user2, created2 = User.get_or_create(
        username="user2", defaults={"email": "user2@example.com"}
    )
    user3, created3 = User.get_or_create(
        username="user3", defaults={"email": "user3@example.com"}
    )

    assert all([created1, created2, created3])
    assert len({user1.id, user2.id, user3.id}) == 3  # All different IDs


def test_get_or_create_field_with_default_value(db):
    """Test get_or_create respects field default values."""
    user, created = User.get_or_create(
        username="defaults_user", email="defaults@example.com"
    )

    assert created is True
    assert user.age == 0  # Field default
    assert user.is_active is True  # Field default


def test_get_or_create_overrides_field_defaults_with_defaults_param(db):
    """Test get_or_create defaults parameter overrides field defaults."""
    user, created = User.get_or_create(
        username="override_user",
        email="override@example.com",
        defaults={"age": 50, "is_active": False},
    )

    assert created is True
    assert user.age == 50  # Overridden
    assert user.is_active is False  # Overridden


def test_get_or_create_with_boolean_field(db):
    """Test get_or_create with boolean field in lookup."""
    User.create(username="active_user", email="active@example.com", is_active=True)

    user, created = User.get_or_create(username="active_user", is_active=True)

    assert created is False
    assert user.is_active is True


def test_get_or_create_different_boolean_creates_new(db):
    """Test get_or_create creates new with different boolean value."""
    User.create(username="bool_user", email="bool1@example.com", is_active=True)

    user, created = User.get_or_create(
        username="bool_user", is_active=False, defaults={"email": "bool2@example.com"}
    )

    assert created is True
    assert user.is_active is False


def test_create_and_get_or_create_integration(db):
    """Test integration between create and get_or_create."""
    # Create using create()
    created_user = User.create(
        username="integration", email="integration@example.com", age=30
    )

    # Get using get_or_create()
    got_user, was_created = User.get_or_create(
        username="integration", defaults={"email": "different@example.com", "age": 40}
    )

    assert was_created is False
    assert got_user.id == created_user.id
    assert got_user.email == "integration@example.com"  # Original value
    assert got_user.age == 30  # Original value


def test_get_or_create_with_integer_lookup(db):
    """Test get_or_create with integer field in lookup."""
    User.create(username="widget_user", email="widget@example.com", age=25)

    user, created = User.get_or_create(
        age=25, defaults={"username": "new_user", "email": "new@example.com"}
    )

    # This should get the existing user because age matches
    assert user.age == 25


def test_get_or_create_idempotent(db):
    """Test get_or_create is idempotent."""
    # Call multiple times with same parameters
    result1 = User.get_or_create(username="idempotent", email="idempotent@example.com")
    result2 = User.get_or_create(username="idempotent", email="idempotent@example.com")
    result3 = User.get_or_create(username="idempotent", email="idempotent@example.com")

    user1, created1 = result1
    user2, created2 = result2
    user3, created3 = result3

    assert created1 is True
    assert created2 is False
    assert created3 is False

    assert user1.id == user2.id == user3.id


def test_create_multiple_with_same_values_creates_duplicates(db):
    """Test that create() allows duplicates (unlike get_or_create)."""
    user1 = User.create(username="duplicate", email="dup@example.com")
    user2 = User.create(username="duplicate", email="dup@example.com")

    assert user1.id != user2.id
    assert user1.username == user2.username

    # Both should exist in database
    all_duplicates = User.filter(username="duplicate")
    assert len(all_duplicates) == 2


def test_get_or_create_complex_scenario(db):
    """Test get_or_create in a complex realistic scenario."""
    # Scenario: Creating articles with unique slugs

    # First article
    article1, created1 = Article.get_or_create(
        slug="python-tutorial",
        defaults={
            "title": "Python Tutorial",
            "content": "Learn Python",
            "published_date": date(2026, 1, 1),
        },
    )
    assert created1 is True

    # Try to create same slug (should get existing)
    article2, created2 = Article.get_or_create(
        slug="python-tutorial",
        defaults={
            "title": "Different Title",  # These defaults won't be used
            "content": "Different content",
            "published_date": date(2026, 1, 2),
        },
    )
    assert created2 is False
    assert article1.id == article2.id
    assert article2.title == "Python Tutorial"  # Original title

    # Create different slug
    article3, created3 = Article.get_or_create(
        slug="django-tutorial",
        defaults={
            "title": "Django Tutorial",
            "content": "Learn Django",
            "published_date": date(2026, 1, 3),
        },
    )
    assert created3 is True
    assert article3.id != article1.id
