# (c) cavaliba.com - tests / telemetry - parse_time, bucket_start, bucket_next (local TZ, DST)

from datetime import UTC, datetime
from zoneinfo import ZoneInfo

from django.test import TestCase

import app_data.telemetry as T

PARIS = ZoneInfo("Europe/Paris")
NOW = datetime(2026, 10, 25, 11, 0, tzinfo=UTC)


class TelemetryTimeTest(TestCase):
    def test_parse_relative(self):
        self.assertEqual(T.parse_time("now", now=NOW), NOW)
        self.assertEqual(T.parse_time(None, now=NOW), NOW)
        self.assertEqual(
            T.parse_time("now-15m", now=NOW), datetime(2026, 10, 25, 10, 45, tzinfo=UTC)
        )
        self.assertEqual(T.parse_time("NOW+2h", now=NOW), datetime(2026, 10, 25, 13, 0, tzinfo=UTC))
        self.assertEqual(T.parse_time("now-1w", now=NOW), datetime(2026, 10, 18, 11, 0, tzinfo=UTC))

    def test_parse_relative_is_elapsed_time_across_dst(self):
        local_now = datetime(2026, 10, 25, 12, 0, tzinfo=PARIS)
        self.assertEqual(
            T.parse_time("now-1d", now=local_now), datetime(2026, 10, 24, 11, 0, tzinfo=UTC)
        )

    def test_parse_absolute_local(self):
        self.assertEqual(T.parse_time("2026-10-01"), datetime(2026, 10, 1, tzinfo=PARIS))
        self.assertEqual(T.parse_time("2026/10/01"), datetime(2026, 10, 1, tzinfo=PARIS))
        self.assertEqual(
            T.parse_time("2026-10-01T10:00"), datetime(2026, 10, 1, 10, 0, tzinfo=PARIS)
        )
        self.assertEqual(
            T.parse_time("2026-10-01T10:00+00:00"), datetime(2026, 10, 1, 10, 0, tzinfo=UTC)
        )

    def test_parse_invalid(self):
        for text in ["yesterday", "now-1y", "2026-13-01"]:
            with self.assertRaises(T.TelemetryError):
                T.parse_time(text)

    def test_bucket_start(self):
        dt = datetime(2026, 11, 18, 14, 37, 12, tzinfo=PARIS)
        expected = {
            "minute": datetime(2026, 11, 18, 14, 37, tzinfo=PARIS),
            "hour": datetime(2026, 11, 18, 14, 0, tzinfo=PARIS),
            "day": datetime(2026, 11, 18, tzinfo=PARIS),
            "week": datetime(2026, 11, 16, tzinfo=PARIS),
            "month": datetime(2026, 11, 1, tzinfo=PARIS),
            "quarter": datetime(2026, 10, 1, tzinfo=PARIS),
            "year": datetime(2026, 1, 1, tzinfo=PARIS),
        }
        for period, start in expected.items():
            self.assertEqual(T.bucket_start(dt, period), start, period)

    def test_bucket_start_dst_day_and_hour(self):
        # 2026-10-25 02:30 after the fall-back (CET, fold=1)
        dt = datetime(2026, 10, 25, 2, 30, tzinfo=PARIS, fold=1)
        self.assertEqual(T.bucket_start(dt, "hour"), datetime(2026, 10, 25, 1, 0, tzinfo=UTC))
        self.assertEqual(T.bucket_start(dt, "day"), datetime(2026, 10, 24, 22, 0, tzinfo=UTC))

    def test_bucket_next(self):
        self.assertEqual(
            T.bucket_next(datetime(2026, 10, 25, 2, 0, tzinfo=PARIS), "hour"),
            datetime(2026, 10, 25, 1, 0, tzinfo=UTC),
        )
        self.assertEqual(
            T.bucket_next(datetime(2026, 10, 25, tzinfo=PARIS), "day"),
            datetime(2026, 10, 26, tzinfo=PARIS),
        )
        self.assertEqual(
            T.bucket_next(datetime(2026, 12, 1, tzinfo=PARIS), "month"),
            datetime(2027, 1, 1, tzinfo=PARIS),
        )
        self.assertEqual(
            T.bucket_next(datetime(2026, 10, 1, tzinfo=PARIS), "quarter"),
            datetime(2027, 1, 1, tzinfo=PARIS),
        )
        self.assertEqual(
            T.bucket_next(datetime(2026, 10, 19, tzinfo=PARIS), "week"),
            datetime(2026, 10, 26, tzinfo=PARIS),
        )
