import pytest
import time
from datetime import datetime, date
from atomsql.orm.models import Model
from atomsql.orm.fields import StringField, TimeStampField


class Event(Model):
    name = StringField()
    scheduled_at = TimeStampField(nullable=True)


class StrictEvent(Model):
    name = StringField()
    scheduled_at = TimeStampField(nullable=False)


def test_timestamp_field_accepts_integer_timestamp(db):
    """TimeStampField should accept integer Unix timestamps."""
    db.register(Event)

    # Current timestamp
    now_ts = int(time.time())
    event = Event(name="Current Event", scheduled_at=now_ts)
    assert event.scheduled_at == now_ts
    assert isinstance(event.scheduled_at, int)


def test_timestamp_field_accepts_zero(db):
    """TimeStampField should accept zero (epoch time)."""
    db.register(Event)

    event = Event(name="Epoch Event", scheduled_at=0)
    assert event.scheduled_at == 0


def test_timestamp_field_accepts_negative_timestamp(db):
    """TimeStampField should accept negative timestamps (before 1970)."""
    db.register(Event)

    # January 1, 1960
    ts = -315619200
    event = Event(name="Historical Event", scheduled_at=ts)
    assert event.scheduled_at == ts
    assert event.scheduled_at < 0


def test_timestamp_field_accepts_large_timestamp(db):
    """TimeStampField should accept timestamps beyond Year 2038 problem."""
    db.register(Event)

    # January 1, 2100 (beyond 32-bit signed int limit of 2147483647)
    ts = 4102444800
    event = Event(name="Future Event", scheduled_at=ts)
    assert event.scheduled_at == ts


def test_timestamp_field_rejects_float(db):
    """TimeStampField should reject float values."""
    db.register(Event)

    with pytest.raises(ValueError, match="expected an int timestamp"):
        Event(name="Float Event", scheduled_at=1234567890.5)


def test_timestamp_field_rejects_string(db):
    """TimeStampField should reject string values."""
    db.register(Event)

    with pytest.raises(ValueError, match="expected an int timestamp"):
        Event(name="String Event", scheduled_at="1234567890")


def test_timestamp_field_rejects_datetime_object(db):
    """TimeStampField should reject datetime objects."""
    db.register(Event)

    dt = datetime(2026, 1, 4, 14, 30, 0)
    with pytest.raises(ValueError, match="expected an int timestamp"):
        Event(name="DateTime Event", scheduled_at=dt)


def test_timestamp_field_rejects_date_object(db):
    """TimeStampField should reject date objects."""
    db.register(Event)

    d = date(2026, 1, 4)
    with pytest.raises(ValueError, match="expected an int timestamp"):
        Event(name="Date Event", scheduled_at=d)


def test_timestamp_field_rejects_invalid_types(db):
    """TimeStampField should reject invalid types."""
    db.register(Event)

    with pytest.raises(ValueError, match="expected an int timestamp"):
        Event(name="Invalid", scheduled_at=[1234567890])

    with pytest.raises(ValueError, match="expected an int timestamp"):
        Event(name="Invalid", scheduled_at={"ts": 1234567890})

    with pytest.raises(ValueError, match="expected an int timestamp"):
        Event(name="Invalid", scheduled_at=None if False else "1234567890")


def test_timestamp_field_null_handling_when_nullable(db):
    """TimeStampField should accept None when nullable=True."""
    db.register(Event)

    event = Event(name="TBD Event", scheduled_at=None)
    assert event.scheduled_at is None

    # Should persist and retrieve None
    event.save()
    retrieved = Event.objects().filter(name="TBD Event")[0]
    assert retrieved.scheduled_at is None


def test_timestamp_field_rejects_null_when_not_nullable(db):
    """TimeStampField should reject None when nullable=False."""
    db.register(StrictEvent)

    with pytest.raises(ValueError, match="cannot be None"):
        StrictEvent(name="Required Event", scheduled_at=None)


def test_timestamp_field_persistence_and_retrieval(db):
    """TimeStampField should correctly persist and retrieve timestamp values."""
    db.register(Event)

    original_ts = 1735992000  # January 4, 2025, 10:00:00 UTC
    event = Event(name="Product Launch", scheduled_at=original_ts)
    event.save()

    retrieved = Event.objects().filter(name="Product Launch")[0]
    assert retrieved.scheduled_at == original_ts
    assert isinstance(retrieved.scheduled_at, int)


def test_timestamp_field_filtering_exact_match(db):
    """TimeStampField should support exact match filtering."""
    db.register(Event)

    ts1 = 1704355200  # January 4, 2024, 10:00:00 UTC
    ts2 = 1704441600  # January 5, 2024, 10:00:00 UTC

    Event(name="Morning Event", scheduled_at=ts1).save()
    Event(name="Afternoon Event", scheduled_at=ts2).save()

    results = list(Event.objects().filter(scheduled_at=ts1))
    assert len(results) == 1
    assert results[0].name == "Morning Event"


def test_timestamp_field_filtering_comparison_operators(db):
    """TimeStampField should support comparison operators in filters."""
    db.register(Event)

    ts1 = 1704067200  # January 1, 2024, 00:00:00 UTC
    ts2 = 1704931200  # January 11, 2024, 00:00:00 UTC
    ts3 = 1706140800  # January 25, 2024, 00:00:00 UTC

    Event(name="Early Event", scheduled_at=ts1).save()
    Event(name="Mid Event", scheduled_at=ts2).save()
    Event(name="Late Event", scheduled_at=ts3).save()

    # Greater than
    later_events = list(Event.objects().filter(scheduled_at__gt=ts1))
    assert len(later_events) == 2

    # Less than or equal
    earlier_events = list(Event.objects().filter(scheduled_at__lte=ts2))
    assert len(earlier_events) == 2

    # Between (using two conditions)
    between_events = list(
        Event.objects().filter(scheduled_at__gt=ts1, scheduled_at__lt=ts3)
    )
    assert len(between_events) == 1
    assert between_events[0].name == "Mid Event"


def test_timestamp_field_with_exists(db):
    """TimeStampField should work with exists() queries."""
    db.register(Event)

    ts = 1704355200
    Event(name="Workshop", scheduled_at=ts).save()

    exists = Event.objects().filter(scheduled_at=ts).exists()
    assert exists is True


def test_timestamp_field_default_value(db):
    """TimeStampField should support default values."""
    default_ts = 1704067200  # January 1, 2024, 00:00:00 UTC

    class ScheduledTask(Model):
        name = StringField()
        scheduled_at = TimeStampField(default=default_ts)

    db.register(ScheduledTask)

    task = ScheduledTask(name="Backup")
    assert task.scheduled_at == default_ts


def test_timestamp_field_update_value(db):
    """TimeStampField should allow updating values."""
    db.register(Event)

    original_ts = 1704355200  # January 4, 2024, 10:00:00 UTC
    new_ts = 1704441600  # January 5, 2024, 10:00:00 UTC

    event = Event(name="Rescheduled Event", scheduled_at=original_ts)
    event.save()

    # Update the timestamp
    event.scheduled_at = new_ts
    event.save()

    retrieved = Event.objects().filter(name="Rescheduled Event")[0]
    assert retrieved.scheduled_at == new_ts


def test_timestamp_field_ordering(db):
    """TimeStampField should support ordering."""
    db.register(Event)

    ts1 = 1704931200  # January 11, 2024
    ts2 = 1704067200  # January 1, 2024
    ts3 = 1706140800  # January 25, 2024

    Event(name="Event2", scheduled_at=ts1).save()
    Event(name="Event1", scheduled_at=ts2).save()
    Event(name="Event3", scheduled_at=ts3).save()

    # Order by timestamp ascending
    ordered_asc = list(Event.objects().order_by("scheduled_at"))
    assert len(ordered_asc) == 3
    assert ordered_asc[0].name == "Event1"
    assert ordered_asc[1].name == "Event2"
    assert ordered_asc[2].name == "Event3"


def test_timestamp_field_unique_constraint(db):
    """TimeStampField should support unique constraint."""

    class UniqueEvent(Model):
        name = StringField()
        scheduled_at = TimeStampField(unique=True, nullable=False)

    db.register(UniqueEvent)

    ts = 1704355200
    event1 = UniqueEvent(name="First", scheduled_at=ts)
    event1.save()

    # Attempting to save another event with same timestamp should be handled by DB
    # (actual constraint enforcement depends on database implementation)


def test_timestamp_field_binary_expressions(db):
    """TimeStampField should support binary expression operators."""
    db.register(Event)

    ts = 1704355200

    # Test __eq__
    expr_eq = Event.scheduled_at == ts
    assert expr_eq.operator == "="
    assert expr_eq.value == ts

    # Test __ne__
    expr_ne = Event.scheduled_at != ts
    assert expr_ne.operator == "!="

    # Test __lt__
    expr_lt = Event.scheduled_at < ts
    assert expr_lt.operator == "<"

    # Test __le__
    expr_le = Event.scheduled_at <= ts
    assert expr_le.operator == "<="

    # Test __gt__
    expr_gt = Event.scheduled_at > ts
    assert expr_gt.operator == ">"

    # Test __ge__
    expr_ge = Event.scheduled_at >= ts
    assert expr_ge.operator == ">="


def test_timestamp_field_sql_type(db):
    """TimeStampField should return correct SQL type for SQLite."""
    db.register(Event)

    field = Event._fields["scheduled_at"]
    field_type = db.backend.get_field_type(field)
    assert field_type == "INTEGER"


def test_timestamp_field_column_name(db):
    """TimeStampField should return correct column name."""
    db.register(Event)

    field = Event._fields["scheduled_at"]
    column_name = field.get_column_name()
    assert column_name == "scheduled_at"


def test_timestamp_field_with_aggregations(db):
    """TimeStampField should work with aggregation queries."""
    db.register(Event)

    ts1 = 1704067200  # January 1, 2024
    ts2 = 1704931200  # January 11, 2024
    ts3 = 1706140800  # January 25, 2024

    Event(name="Event1", scheduled_at=ts1).save()
    Event(name="Event2", scheduled_at=ts2).save()
    Event(name="Event3", scheduled_at=ts3).save()

    # Count events
    count = Event.objects().count()
    assert count == 3


def test_timestamp_field_exclude_filter(db):
    """TimeStampField should work with exclude() filters."""
    db.register(Event)

    ts1 = 1704067200  # January 1, 2024
    ts2 = 1706140800  # January 25, 2024

    Event(name="Early", scheduled_at=ts1).save()
    Event(name="Late", scheduled_at=ts2).save()

    # Exclude specific timestamp
    results = list(Event.objects().exclude(scheduled_at=ts1))
    assert len(results) == 1
    assert results[0].name == "Late"


def test_timestamp_field_year_2038_problem(db):
    """TimeStampField should handle timestamps beyond Year 2038 (32-bit limit)."""
    db.register(Event)

    # January 19, 2038, 03:14:07 UTC (32-bit signed int max)
    ts_2038 = 2147483647

    # One second later (would overflow 32-bit signed int)
    ts_overflow = 2147483648

    # Far future
    ts_future = 4102444800  # January 1, 2100

    Event(name="2038 Event", scheduled_at=ts_2038).save()
    Event(name="Overflow Event", scheduled_at=ts_overflow).save()
    Event(name="Future Event", scheduled_at=ts_future).save()

    retrieved_2038 = Event.objects().filter(name="2038 Event")[0]
    assert retrieved_2038.scheduled_at == ts_2038

    retrieved_overflow = Event.objects().filter(name="Overflow Event")[0]
    assert retrieved_overflow.scheduled_at == ts_overflow

    retrieved_future = Event.objects().filter(name="Future Event")[0]
    assert retrieved_future.scheduled_at == ts_future


def test_timestamp_field_min_max_values(db):
    """TimeStampField should handle very large positive and negative timestamps."""
    db.register(Event)

    # Test large negative (far past)
    ts_negative = -2147483648  # Around year 1901
    event_past = Event(name="Far Past", scheduled_at=ts_negative)
    event_past.save()

    # Test large positive (far future)
    ts_positive = 9999999999  # Year 2286
    event_future = Event(name="Far Future", scheduled_at=ts_positive)
    event_future.save()

    retrieved_past = Event.objects().filter(name="Far Past")[0]
    assert retrieved_past.scheduled_at == ts_negative

    retrieved_future = Event.objects().filter(name="Far Future")[0]
    assert retrieved_future.scheduled_at == ts_positive


def test_timestamp_field_multiple_records_ordering(db):
    """TimeStampField should correctly order multiple records."""
    db.register(Event)

    # Create events in random order
    timestamps = [
        (1704931200, "Mid"),  # January 11, 2024
        (1704067200, "Early"),  # January 1, 2024
        (1706140800, "Late"),  # January 25, 2024
        (1704499200, "Mid-Early"),  # January 6, 2024
    ]

    for ts, name in timestamps:
        Event(name=name, scheduled_at=ts).save()

    # Get all ordered by timestamp
    ordered = list(Event.objects().order_by("scheduled_at"))

    assert ordered[0].name == "Early"
    assert ordered[1].name == "Mid-Early"
    assert ordered[2].name == "Mid"
    assert ordered[3].name == "Late"


def test_timestamp_field_range_queries(db):
    """TimeStampField should support range queries."""
    db.register(Event)

    # Create events across a range
    base_ts = 1704067200  # January 1, 2024, 00:00:00

    for i in range(10):
        ts = base_ts + (i * 86400)  # Add one day each
        Event(name=f"Day {i+1}", scheduled_at=ts).save()

    # Query for events in the middle of the range
    start_ts = base_ts + (3 * 86400)  # Day 4
    end_ts = base_ts + (7 * 86400)  # Day 8

    range_events = list(
        Event.objects().filter(scheduled_at__gte=start_ts, scheduled_at__lte=end_ts)
    )

    assert len(range_events) == 5  # Days 4, 5, 6, 7, 8
    assert range_events[0].name == "Day 4"
    assert range_events[-1].name == "Day 8"


# PostgreSQL-specific tests
@pytest.mark.postgres
def test_timestamp_field_postgres_bigint_type(db):
    """Test that PostgreSQL stores TimeStampField as BIGINT."""
    # Skip if not PostgreSQL
    if not db.backend.__class__.__name__ == "PostgresBackend":
        pytest.skip("PostgreSQL-only test")

    db.register(Event)

    # Check field type in PostgreSQL
    if hasattr(db.backend, "get_field_type"):
        field = Event._fields["scheduled_at"]
        field_type = db.backend.get_field_type(field)
        assert field_type == "BIGINT"


@pytest.mark.postgres
def test_timestamp_field_postgres_year_2038_support(db):
    """Test that PostgreSQL BIGINT prevents Year 2038 problem."""
    # Skip if not PostgreSQL
    if not db.backend.__class__.__name__ == "PostgresBackend":
        pytest.skip("PostgreSQL-only test")

    db.register(Event)

    # Timestamp beyond 32-bit signed int limit
    ts_overflow = 2147483648  # One second after Year 2038 problem
    ts_far_future = 253402300799  # December 31, 9999, 23:59:59 UTC

    event1 = Event(name="Post-2038", scheduled_at=ts_overflow)
    event1.save()

    event2 = Event(name="Far Future", scheduled_at=ts_far_future)
    event2.save()

    # Should retrieve without overflow
    retrieved1 = Event.objects().filter(name="Post-2038")[0]
    assert retrieved1.scheduled_at == ts_overflow

    retrieved2 = Event.objects().filter(name="Far Future")[0]
    assert retrieved2.scheduled_at == ts_far_future


@pytest.mark.postgres
def test_timestamp_field_postgres_performance(db):
    """Test PostgreSQL BIGINT indexing and performance."""
    # Skip if not PostgreSQL
    if not db.backend.__class__.__name__ == "PostgresBackend":
        pytest.skip("PostgreSQL-only test")

    db.register(Event)

    # PostgreSQL should efficiently handle BIGINT timestamp queries
    base_ts = 1704067200

    # Create multiple events
    for i in range(100):
        Event(name=f"Event {i}", scheduled_at=base_ts + i).save()

    # Query should be efficient with BIGINT
    target_ts = base_ts + 50
    results = list(Event.objects().filter(scheduled_at=target_ts))
    assert len(results) == 1
    assert results[0].name == "Event 50"


# SQLite-specific tests
@pytest.mark.sqlite
def test_timestamp_field_sqlite_integer_type(db):
    """Test that SQLite stores TimeStampField as INTEGER."""
    db.register(Event)

    field = Event._fields["scheduled_at"]
    field_type = db.backend.get_field_type(field)
    assert field_type == "INTEGER"


@pytest.mark.sqlite
def test_timestamp_field_sqlite_storage(db):
    """Test that SQLite stores timestamps as integers."""
    db.register(Event)

    ts = 1704355200
    event = Event(name="SQLite Test", scheduled_at=ts)
    event.save()

    # Check the stored format
    cursor = db.backend.connection.cursor()
    cursor.execute("SELECT scheduled_at FROM event WHERE name = 'SQLite Test'")
    result = cursor.fetchone()

    if result and result[0]:
        stored_value = result[0]
        # Should be stored as integer
        assert isinstance(stored_value, int)
        assert stored_value == ts


@pytest.mark.sqlite
def test_timestamp_field_sqlite_null_handling(db):
    """Test SQLite handling of NULL timestamps."""
    db.register(Event)

    event = Event(name="No Time", scheduled_at=None)
    event.save()

    cursor = db.backend.connection.cursor()
    cursor.execute("SELECT scheduled_at FROM event WHERE name = 'No Time'")
    result = cursor.fetchone()

    # Should store as NULL
    assert result[0] is None

    # Should retrieve as None
    retrieved = Event.objects().filter(name="No Time")[0]
    assert retrieved.scheduled_at is None
