"""Comprehensive test suite for Model.update_or_create() method."""

import pytest
from datetime import datetime, date
from atomsql import Database, Model, StringField, IntegerField
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)
    description = StringField(nullable=True)
    stock = IntegerField(default=0)


class Article(Model):
    title = StringField()
    slug = StringField(unique=True)
    content = StringField()
    view_count = IntegerField(default=0)
    published_date = DateField(nullable=True)
    updated_at = DateTimeField(nullable=True)


class Profile(Model):
    username = StringField(unique=True)
    bio = StringField(nullable=True)
    location = StringField(nullable=True)
    website = 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.update_or_create() - CREATE scenarios
# ============================================================================


def test_update_or_create_creates_when_not_exists(db):
    """Test update_or_create creates a new instance when it doesn't exist."""
    user, created = User.update_or_create(
        username="new_user", defaults={"email": "new@example.com", "age": 25}
    )

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


def test_update_or_create_creates_with_all_fields(db):
    """Test update_or_create creates with all fields specified."""
    user, created = User.update_or_create(
        username="complete_user",
        defaults={"email": "complete@example.com", "age": 30, "is_active": False},
    )

    assert created is True
    assert user.username == "complete_user"
    assert user.email == "complete@example.com"
    assert user.age == 30
    assert user.is_active is False


def test_update_or_create_creates_with_field_defaults(db):
    """Test update_or_create respects model field defaults on create."""
    user, created = User.update_or_create(
        username="defaults_user", defaults={"email": "defaults@example.com"}
    )

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


def test_update_or_create_creates_with_no_defaults(db):
    """Test update_or_create creates with only lookup fields."""
    user, created = User.update_or_create(
        username="minimal", email="minimal@example.com"
    )

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


def test_update_or_create_creates_persists_to_database(db):
    """Test update_or_create persists new instances to database."""
    user, created = User.update_or_create(
        username="persisted", 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 == "persisted"


# ============================================================================
# Tests for Model.update_or_create() - UPDATE scenarios
# ============================================================================


def test_update_or_create_updates_existing_instance(db):
    """Test update_or_create updates an existing instance."""
    # Create initial user
    User.create(username="existing", email="old@example.com", age=25)

    # Update or create with new values
    user, created = User.update_or_create(
        username="existing", defaults={"email": "new@example.com", "age": 30}
    )

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


def test_update_or_create_updates_single_field(db):
    """Test update_or_create updates only specified fields."""
    # Create initial user
    User.create(username="partial", email="original@example.com", age=25)

    # Update only email
    user, created = User.update_or_create(
        username="partial", defaults={"email": "updated@example.com"}
    )

    assert created is False
    assert user.email == "updated@example.com"
    assert user.age == 25  # Not changed


def test_update_or_create_updates_persists_to_database(db):
    """Test update_or_create persists updates to database."""
    # Create initial user
    original = User.create(username="updateable", email="old@example.com", age=20)
    original_id = original.id

    # Update
    user, created = User.update_or_create(
        username="updateable", defaults={"email": "new@example.com", "age": 25}
    )

    assert created is False
    assert user.id == original_id

    # Verify in database
    retrieved = User.get(original_id)
    assert retrieved.email == "new@example.com"
    assert retrieved.age == 25


def test_update_or_create_updates_multiple_fields(db):
    """Test update_or_create updates multiple fields at once."""
    User.create(username="multi", email="old@example.com", age=20, is_active=True)

    user, created = User.update_or_create(
        username="multi",
        defaults={"email": "new@example.com", "age": 35, "is_active": False},
    )

    assert created is False
    assert user.email == "new@example.com"
    assert user.age == 35
    assert user.is_active is False


def test_update_or_create_updates_with_empty_defaults(db):
    """Test update_or_create with empty defaults doesn't change existing."""
    User.create(username="unchanged", email="original@example.com", age=25)

    user, created = User.update_or_create(username="unchanged", defaults={})

    assert created is False
    assert user.email == "original@example.com"
    assert user.age == 25


def test_update_or_create_updates_with_none_defaults(db):
    """Test update_or_create with None defaults parameter."""
    User.create(username="no_defaults", email="original@example.com", age=25)

    user, created = User.update_or_create(username="no_defaults")

    assert created is False
    assert user.email == "original@example.com"
    assert user.age == 25


def test_update_or_create_updates_nullable_field_to_none(db):
    """Test update_or_create can set nullable field to None."""
    Profile.create(username="profile1", bio="Original bio", location="NYC")

    profile, created = Profile.update_or_create(
        username="profile1", defaults={"bio": None, "location": "LA"}
    )

    assert created is False
    assert profile.bio is None  # Updated to None
    assert profile.location == "LA"


def test_update_or_create_updates_nullable_field_from_none(db):
    """Test update_or_create can update nullable field from None."""
    Profile.create(username="profile2", bio=None)

    profile, created = Profile.update_or_create(
        username="profile2", defaults={"bio": "New bio"}
    )

    assert created is False
    assert profile.bio == "New bio"


# ============================================================================
# Tests for Model.update_or_create() - Multiple lookup fields
# ============================================================================


def test_update_or_create_with_multiple_lookup_fields(db):
    """Test update_or_create with multiple lookup fields."""
    User.create(username="multi_lookup", email="multi@example.com", age=25)

    user, created = User.update_or_create(
        username="multi_lookup", email="multi@example.com", defaults={"age": 30}
    )

    assert created is False
    assert user.age == 30


def test_update_or_create_creates_when_multiple_lookups_dont_match(db):
    """Test update_or_create creates when multiple lookup fields don't match."""
    User.create(username="user1", email="email1@example.com", age=20)

    # Different email means no match
    user, created = User.update_or_create(
        username="user1", email="email2@example.com", defaults={"age": 30}
    )

    assert created is True
    assert user.email == "email2@example.com"


# ============================================================================
# Tests for Model.update_or_create() - Field type tests
# ============================================================================


def test_update_or_create_with_boolean_field(db):
    """Test update_or_create with boolean field."""
    User.create(username="bool_user", email="bool@example.com", is_active=True)

    user, created = User.update_or_create(
        username="bool_user", defaults={"is_active": False}
    )

    assert created is False
    assert user.is_active is False


def test_update_or_create_with_integer_field(db):
    """Test update_or_create with integer field."""
    Product.create(name="Widget", sku="WID-001", stock=10)

    product, created = Product.update_or_create(sku="WID-001", defaults={"stock": 100})

    assert created is False
    assert product.stock == 100


def test_update_or_create_with_date_field(db):
    """Test update_or_create with date field."""
    today = date.today()
    tomorrow = date(today.year, today.month, today.day + 1) if today.day < 28 else today

    Article.create(
        title="Article", slug="article-1", content="Content", published_date=today
    )

    article, created = Article.update_or_create(
        slug="article-1", defaults={"published_date": tomorrow}
    )

    assert created is False
    assert article.published_date == tomorrow


def test_update_or_create_with_datetime_field(db):
    """Test update_or_create with datetime field."""
    now = datetime.now()
    later = datetime(now.year, now.month, now.day, now.hour + 1, now.minute, now.second)

    Article.create(
        title="Timestamped", slug="timestamped-1", content="Content", updated_at=now
    )

    article, created = Article.update_or_create(
        slug="timestamped-1", defaults={"updated_at": later}
    )

    assert created is False
    assert article.updated_at == later


# ============================================================================
# Tests for Model.update_or_create() - Edge cases and special scenarios
# ============================================================================


def test_update_or_create_returns_tuple(db):
    """Test update_or_create always returns a tuple."""
    result = User.update_or_create(
        username="tuple_test", defaults={"email": "tuple@example.com"}
    )

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


def test_update_or_create_idempotent_updates(db):
    """Test update_or_create is idempotent for updates."""
    User.create(username="idempotent", email="original@example.com", age=25)

    # Call multiple times with same defaults
    user1, created1 = User.update_or_create(
        username="idempotent", defaults={"email": "new@example.com", "age": 30}
    )

    user2, created2 = User.update_or_create(
        username="idempotent", defaults={"email": "new@example.com", "age": 30}
    )

    assert created1 is False
    assert created2 is False
    assert user1.id == user2.id
    assert user1.email == user2.email == "new@example.com"
    assert user1.age == user2.age == 30


def test_update_or_create_without_db_registration(db):
    """Test update_or_create without database registration raises error."""

    class UnregisteredModel(Model):
        name = StringField()

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

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


def test_update_or_create_with_unique_field_lookup(db):
    """Test update_or_create using unique field as lookup."""
    Product.create(name="Original Product", sku="UNIQUE-SKU", stock=10)

    product, created = Product.update_or_create(
        sku="UNIQUE-SKU", defaults={"name": "Updated Product", "stock": 50}
    )

    assert created is False
    assert product.name == "Updated Product"
    assert product.stock == 50


def test_update_or_create_incremental_updates(db):
    """Test update_or_create can be used for incremental updates."""
    Article.create(
        title="Article", slug="view-counter", content="Content", view_count=0
    )

    # Simulate multiple views
    for i in range(1, 6):
        article, created = Article.update_or_create(
            slug="view-counter", defaults={"view_count": i}
        )
        assert created is False
        assert article.view_count == i

    # Verify final count
    final = Article.filter(slug="view-counter").first()
    assert final.view_count == 5


def test_update_or_create_updates_preserve_unchanged_fields(db):
    """Test update_or_create preserves fields not in defaults."""
    User.create(
        username="preserve", email="preserve@example.com", age=25, is_active=True
    )

    # Update only email
    user, created = User.update_or_create(
        username="preserve", defaults={"email": "new@example.com"}
    )

    assert created is False
    assert user.email == "new@example.com"  # Updated
    assert user.age == 25  # Preserved
    assert user.is_active is True  # Preserved


def test_update_or_create_handles_race_condition(db):
    """Test update_or_create handles race conditions gracefully."""
    # Create initial record
    User.create(username="race_test", email="race@example.com", age=20)

    # Multiple concurrent-like updates
    user1, created1 = User.update_or_create(username="race_test", defaults={"age": 25})

    user2, created2 = User.update_or_create(username="race_test", defaults={"age": 30})

    assert created1 is False
    assert created2 is False
    assert user1.id == user2.id
    # Last update wins
    assert user2.age == 30


def test_update_or_create_complex_workflow(db):
    """Test update_or_create in a complex realistic workflow."""
    # Scenario: User profile management

    # First access - create profile
    profile1, created1 = Profile.update_or_create(
        username="john_doe",
        defaults={
            "bio": "Software developer",
            "location": "San Francisco",
            "website": "https://johndoe.com",
        },
    )
    assert created1 is True
    assert profile1.bio == "Software developer"

    # Update bio
    profile2, created2 = Profile.update_or_create(
        username="john_doe", defaults={"bio": "Senior software engineer"}
    )
    assert created2 is False
    assert profile2.id == profile1.id
    assert profile2.bio == "Senior software engineer"
    assert profile2.location == "San Francisco"  # Unchanged

    # Update location
    profile3, created3 = Profile.update_or_create(
        username="john_doe", defaults={"location": "New York"}
    )
    assert created3 is False
    assert profile3.bio == "Senior software engineer"  # Still has previous update
    assert profile3.location == "New York"


def test_update_or_create_multiple_records(db):
    """Test update_or_create with multiple different records."""
    # Create/update multiple users
    users = []
    for i in range(5):
        user, created = User.update_or_create(
            username=f"user{i}",
            defaults={"email": f"user{i}@example.com", "age": 20 + i},
        )
        users.append(user)
        assert created is True

    # Update all users
    for i in range(5):
        user, created = User.update_or_create(
            username=f"user{i}", defaults={"age": 30 + i}
        )
        assert created is False
        assert user.age == 30 + i
        assert user.id == users[i].id


def test_update_or_create_lookup_by_id_behavior(db):
    """Test update_or_create when lookup includes id field."""
    original = User.create(username="id_user", email="id@example.com", age=25)

    # Lookup by id
    user, created = User.update_or_create(id=original.id, defaults={"age": 30})

    assert created is False
    assert user.id == original.id
    assert user.age == 30


def test_update_or_create_with_all_nullable_defaults(db):
    """Test update_or_create with all nullable fields."""
    profile, created = Profile.update_or_create(
        username="nullable_profile",
        defaults={"bio": None, "location": None, "website": None},
    )

    assert created is True
    assert profile.bio is None
    assert profile.location is None
    assert profile.website is None


def test_update_or_create_alternating_create_update(db):
    """Test alternating between different users shows correct create/update."""
    # Create user1
    user1a, created1a = User.update_or_create(
        username="user_a", defaults={"email": "a@example.com", "age": 20}
    )
    assert created1a is True

    # Create user2
    user2a, created2a = User.update_or_create(
        username="user_b", defaults={"email": "b@example.com", "age": 25}
    )
    assert created2a is True

    # Update user1
    user1b, created1b = User.update_or_create(username="user_a", defaults={"age": 21})
    assert created1b is False
    assert user1b.id == user1a.id

    # Update user2
    user2b, created2b = User.update_or_create(username="user_b", defaults={"age": 26})
    assert created2b is False
    assert user2b.id == user2a.id


def test_update_or_create_comparison_with_get_or_create(db):
    """Test that update_or_create differs from get_or_create in update behavior."""
    # Initial record
    User.create(username="compare", email="original@example.com", age=20)

    # get_or_create doesn't update
    user1, created1 = User.get_or_create(
        username="compare", defaults={"email": "new1@example.com", "age": 25}
    )
    assert created1 is False
    assert user1.email == "original@example.com"  # Not updated
    assert user1.age == 20  # Not updated

    # update_or_create does update
    user2, created2 = User.update_or_create(
        username="compare", defaults={"email": "new2@example.com", "age": 30}
    )
    assert created2 is False
    assert user2.email == "new2@example.com"  # Updated
    assert user2.age == 30  # Updated
