"""Tests for django_devicehub.storage.django_orm — DjangoORMStorage."""

from datetime import timedelta

import pytest
from django.utils import timezone

from django_devicehub.storage.django_orm import DjangoORMStorage

from tests.testapp.models import (
    WeatherStationDevice,
    WeatherStationReading,
)


@pytest.fixture
def storage():
    return DjangoORMStorage()


@pytest.fixture
def device_with_readings(db):
    """A device with several readings spread over time."""
    device = WeatherStationDevice.objects.create(
        device_id="storage-ws-001",
        name="Storage Test Station",
    )
    base_time = timezone.now() - timedelta(hours=5)

    for i in range(10):
        WeatherStationReading.objects.create(
            device=device,
            timestamp=base_time + timedelta(hours=i * 0.5),
            temperature=20.0 + i,
            humidity=50.0 + i * 2,
        )

    return device, base_time


# ---------------------------------------------------------------------------
# store_reading()
# ---------------------------------------------------------------------------


class TestStoreReading:
    @pytest.mark.django_db
    def test_creates_a_reading(self, storage):
        device = WeatherStationDevice.objects.create(
            device_id="store-ws-001",
            name="Store Test",
        )
        ts = timezone.now()
        reading = storage.store_reading(
            WeatherStationReading,
            device,
            ts,
            {"temperature": 23.5, "humidity": 65.0},
        )
        assert reading.pk is not None
        assert reading.device == device
        assert reading.timestamp == ts
        assert reading.temperature == 23.5
        assert reading.humidity == 65.0

    @pytest.mark.django_db
    def test_partial_data(self, storage):
        device = WeatherStationDevice.objects.create(
            device_id="store-ws-002",
        )
        ts = timezone.now()
        reading = storage.store_reading(
            WeatherStationReading,
            device,
            ts,
            {"temperature": 18.0},
        )
        assert reading.temperature == 18.0
        assert reading.humidity is None


# ---------------------------------------------------------------------------
# query_readings()
# ---------------------------------------------------------------------------


class TestQueryReadings:
    @pytest.mark.django_db
    def test_query_all(self, storage, device_with_readings):
        device, _ = device_with_readings
        qs = storage.query_readings(WeatherStationReading, device=device)
        assert qs.count() == 10

    @pytest.mark.django_db
    def test_query_with_time_range(self, storage, device_with_readings):
        device, base_time = device_with_readings
        start = base_time + timedelta(hours=1)
        end = base_time + timedelta(hours=3)
        qs = storage.query_readings(
            WeatherStationReading,
            device=device,
            start=start,
            end=end,
        )
        # Readings at 1h, 1.5h, 2h, 2.5h, 3h = 5 readings
        assert qs.count() >= 3  # At least some in range

    @pytest.mark.django_db
    def test_query_with_limit(self, storage, device_with_readings):
        device, _ = device_with_readings
        qs = storage.query_readings(
            WeatherStationReading,
            device=device,
            limit=3,
        )
        assert len(list(qs)) == 3

    @pytest.mark.django_db
    def test_query_with_fields(self, storage, device_with_readings):
        device, _ = device_with_readings
        qs = storage.query_readings(
            WeatherStationReading,
            device=device,
            fields=["temperature"],
        )
        # When fields are specified, returns dicts via .values()
        first = qs.first()
        assert isinstance(first, dict)
        assert "temperature" in first
        assert "device_id" in first
        assert "timestamp" in first

    @pytest.mark.django_db
    def test_query_with_ascending_order(self, storage, device_with_readings):
        device, _ = device_with_readings
        qs = storage.query_readings(
            WeatherStationReading,
            device=device,
            order="timestamp",
        )
        timestamps = list(qs.values_list("timestamp", flat=True))
        assert timestamps == sorted(timestamps)

    @pytest.mark.django_db
    def test_query_without_device_filter(self, storage, device_with_readings):
        """query_readings with device=None should return all readings."""
        qs = storage.query_readings(WeatherStationReading)
        assert qs.count() == 10


# ---------------------------------------------------------------------------
# aggregate() — simple
# ---------------------------------------------------------------------------


class TestAggregateSimple:
    @pytest.mark.django_db
    def test_avg(self, storage, device_with_readings):
        device, _ = device_with_readings
        result = storage.aggregate(
            WeatherStationReading,
            "temperature",
            function="avg",
            device=device,
        )
        # Temperatures are 20, 21, 22, ..., 29 -> avg = 24.5
        assert "result" in result
        assert abs(result["result"] - 24.5) < 0.01

    @pytest.mark.django_db
    def test_min(self, storage, device_with_readings):
        device, _ = device_with_readings
        result = storage.aggregate(
            WeatherStationReading,
            "temperature",
            function="min",
            device=device,
        )
        assert result["result"] == 20.0

    @pytest.mark.django_db
    def test_max(self, storage, device_with_readings):
        device, _ = device_with_readings
        result = storage.aggregate(
            WeatherStationReading,
            "temperature",
            function="max",
            device=device,
        )
        assert result["result"] == 29.0

    @pytest.mark.django_db
    def test_sum(self, storage, device_with_readings):
        device, _ = device_with_readings
        result = storage.aggregate(
            WeatherStationReading,
            "temperature",
            function="sum",
            device=device,
        )
        # Sum of 20..29 = 245
        assert result["result"] == 245.0

    @pytest.mark.django_db
    def test_count(self, storage, device_with_readings):
        device, _ = device_with_readings
        result = storage.aggregate(
            WeatherStationReading,
            "temperature",
            function="count",
            device=device,
        )
        assert result["result"] == 10

    @pytest.mark.django_db
    def test_unknown_function_raises(self, storage, device_with_readings):
        device, _ = device_with_readings
        with pytest.raises(ValueError, match="Unknown aggregate function"):
            storage.aggregate(
                WeatherStationReading,
                "temperature",
                function="median",
                device=device,
            )

    @pytest.mark.django_db
    def test_aggregate_with_time_range(self, storage, device_with_readings):
        device, base_time = device_with_readings
        start = base_time + timedelta(hours=2)
        result = storage.aggregate(
            WeatherStationReading,
            "temperature",
            function="count",
            device=device,
            start=start,
        )
        # Only readings from hour 2 onward
        assert result["result"] < 10


# ---------------------------------------------------------------------------
# aggregate() — time-bucketed
# ---------------------------------------------------------------------------


class TestAggregateBucketed:
    @pytest.mark.django_db
    def test_bucketed_by_hour(self, storage, device_with_readings):
        device, _ = device_with_readings
        result = storage.aggregate(
            WeatherStationReading,
            "temperature",
            function="avg",
            device=device,
            interval="hour",
        )
        # Result should be a queryset of dicts with 'bucket' and 'result' keys
        buckets = list(result)
        assert len(buckets) >= 1
        assert "bucket" in buckets[0]
        assert "result" in buckets[0]

    @pytest.mark.django_db
    def test_bucketed_by_day(self, storage, device_with_readings):
        device, _ = device_with_readings
        result = storage.aggregate(
            WeatherStationReading,
            "temperature",
            function="avg",
            device=device,
            interval="day",
        )
        buckets = list(result)
        assert len(buckets) >= 1

    @pytest.mark.django_db
    def test_bucketed_by_timedelta(self, storage, device_with_readings):
        device, _ = device_with_readings
        result = storage.aggregate(
            WeatherStationReading,
            "temperature",
            function="avg",
            device=device,
            interval=timedelta(hours=1),
        )
        buckets = list(result)
        assert len(buckets) >= 1

    @pytest.mark.django_db
    def test_interval_to_trunc_minute(self, storage):
        assert storage._interval_to_trunc("minute") == "minute"

    @pytest.mark.django_db
    def test_interval_to_trunc_unknown_string(self, storage):
        assert storage._interval_to_trunc("fortnight") == "hour"

    @pytest.mark.django_db
    def test_interval_to_trunc_short_timedelta(self, storage):
        assert storage._interval_to_trunc(timedelta(seconds=30)) == "minute"

    @pytest.mark.django_db
    def test_interval_to_trunc_medium_timedelta(self, storage):
        assert storage._interval_to_trunc(timedelta(minutes=30)) == "hour"

    @pytest.mark.django_db
    def test_interval_to_trunc_day_timedelta(self, storage):
        assert storage._interval_to_trunc(timedelta(hours=12)) == "day"

    @pytest.mark.django_db
    def test_interval_to_trunc_week_timedelta(self, storage):
        assert storage._interval_to_trunc(timedelta(days=5)) == "week"

    @pytest.mark.django_db
    def test_interval_to_trunc_month_timedelta(self, storage):
        assert storage._interval_to_trunc(timedelta(days=60)) == "month"

    @pytest.mark.django_db
    def test_interval_to_trunc_non_string_non_timedelta(self, storage):
        assert storage._interval_to_trunc(12345) == "hour"
