"""Tests for app/services/timezone.py (FU10-energy-tz-accrual).

All tests monkeypatch ``local_tz`` to a fixed zone so they are deterministic
on any CI host timezone.
"""

from __future__ import annotations

from datetime import UTC, date, datetime
from zoneinfo import ZoneInfo

import pytest

from app.services import timezone as tz_mod


# ---------------------------------------------------------------------------
# Helpers
# ---------------------------------------------------------------------------


@pytest.fixture()
def ams(monkeypatch):
    """Pin local_tz to Europe/Amsterdam for all tests in this module."""
    monkeypatch.setattr(tz_mod, "local_tz", lambda: ZoneInfo("Europe/Amsterdam"))


@pytest.fixture()
def utc_tz(monkeypatch):
    """Pin local_tz to UTC for simple arithmetic tests."""
    monkeypatch.setattr(tz_mod, "local_tz", lambda: ZoneInfo("UTC"))


# ---------------------------------------------------------------------------
# local_tz() — resolution and monkeypatch contract
# ---------------------------------------------------------------------------


def test_local_tz_returns_tzinfo(ams):
    """local_tz() must return a tzinfo object."""
    tz = tz_mod.local_tz()
    assert tz is not None
    # Verify it behaves like a valid tzinfo.
    d = datetime(2026, 6, 25, 12, 0, tzinfo=tz)
    assert d.utcoffset() is not None


def test_local_tz_env_var_overrides(monkeypatch):
    """When TZ env var is set, local_tz() returns that zone."""
    monkeypatch.setenv("TZ", "America/New_York")
    tz = tz_mod.local_tz()
    # ZoneInfo("America/New_York") should have UTC-5 or UTC-4 offset.
    d = datetime(2026, 1, 1, 12, 0, tzinfo=tz)  # January → UTC-5
    offset_hours = d.utcoffset().total_seconds() / 3600
    assert offset_hours == pytest.approx(-5.0)
    monkeypatch.delenv("TZ")


# ---------------------------------------------------------------------------
# to_local() — conversion correctness
# ---------------------------------------------------------------------------


def test_to_local_aware_utc(ams):
    """Aware UTC datetime must convert to CEST (UTC+2) in summer."""
    dt_utc = datetime(2026, 6, 25, 12, 0, tzinfo=UTC)
    local = tz_mod.to_local(dt_utc)
    assert local.tzinfo is not None
    assert local.hour == 14  # CEST = UTC+2


def test_to_local_naive_treated_as_utc(ams):
    """Naive datetime must be treated as UTC and converted to local."""
    naive = datetime(2026, 6, 25, 12, 0)  # naive = UTC convention
    local = tz_mod.to_local(naive)
    assert local.hour == 14  # CEST = UTC+2


def test_to_local_winter_offset(monkeypatch):
    """CET (UTC+1) applies in January."""
    monkeypatch.setattr(tz_mod, "local_tz", lambda: ZoneInfo("Europe/Amsterdam"))
    dt_utc = datetime(2026, 1, 15, 12, 0, tzinfo=UTC)
    local = tz_mod.to_local(dt_utc)
    assert local.hour == 13  # CET = UTC+1


# ---------------------------------------------------------------------------
# local_date() — date extraction
# ---------------------------------------------------------------------------


def test_local_date_around_cest_midnight(ams):
    """UTC 22:00 on June 24 is local midnight June 25 in CEST."""
    # CEST = UTC+2; June 24 22:00 UTC = June 25 00:00 CEST
    dt = datetime(2026, 6, 24, 22, 0, tzinfo=UTC)
    d = tz_mod.local_date(dt)
    assert d == date(2026, 6, 25)


def test_local_date_just_before_cest_midnight(ams):
    """UTC 21:59 on June 24 is still June 24 in CEST (23:59 local)."""
    dt = datetime(2026, 6, 24, 21, 59, tzinfo=UTC)
    d = tz_mod.local_date(dt)
    assert d == date(2026, 6, 24)


# ---------------------------------------------------------------------------
# local_midnight_utc() — round-trip correctness
# ---------------------------------------------------------------------------


def test_local_midnight_utc_cest_summer(ams):
    """Europe/Amsterdam local midnight June 25 → June 24 22:00 UTC (CEST=UTC+2)."""
    utc_midnight = tz_mod.local_midnight_utc(date(2026, 6, 25))
    expected = datetime(2026, 6, 24, 22, 0, tzinfo=UTC)
    assert utc_midnight == expected


def test_local_midnight_utc_cet_winter(monkeypatch):
    """Europe/Amsterdam local midnight Jan 15 → Jan 14 23:00 UTC (CET=UTC+1)."""
    monkeypatch.setattr(tz_mod, "local_tz", lambda: ZoneInfo("Europe/Amsterdam"))
    utc_midnight = tz_mod.local_midnight_utc(date(2026, 1, 15))
    expected = datetime(2026, 1, 14, 23, 0, tzinfo=UTC)
    assert utc_midnight == expected


def test_local_midnight_utc_roundtrip(ams):
    """local_date(local_midnight_utc(D)) must return D for any date D."""
    for day in range(1, 32):
        d = date(2026, 6, day) if day <= 30 else date(2026, 7, day - 30)
        if day > 30:
            continue
        midnight_utc = tz_mod.local_midnight_utc(d)
        recovered = tz_mod.local_date(midnight_utc)
        assert recovered == d, f"Round-trip failed for {d}"


# ---------------------------------------------------------------------------
# local_now() — sanity check
# ---------------------------------------------------------------------------


def test_local_now_is_aware(ams):
    """local_now() must return a timezone-aware datetime."""
    now = tz_mod.local_now()
    assert now.tzinfo is not None
    assert now.utcoffset() is not None


# ---------------------------------------------------------------------------
# effective_from naive→local→UTC (Principle A — contract routes)
# ---------------------------------------------------------------------------


def test_localize_naive_effective_from(monkeypatch):
    """A naive '2026-06-25T00:00:00' must be interpreted as CEST local midnight.

    Expected: CEST midnight June 25 = UTC June 24 22:00.
    """
    monkeypatch.setattr(tz_mod, "local_tz", lambda: ZoneInfo("Europe/Amsterdam"))
    # Simulate what the route helper does:
    from app.api.routes.api.energy_contracts import _localize_effective_from
    naive = datetime(2026, 6, 25, 0, 0, 0)  # naive, no tzinfo
    utc_result = _localize_effective_from(naive)
    assert utc_result.tzinfo is not None
    expected = datetime(2026, 6, 24, 22, 0, 0, tzinfo=UTC)
    assert utc_result == expected, (
        f"Naive local midnight should localize to {expected}, got {utc_result}"
    )


def test_localize_aware_effective_from():
    """An aware datetime must be stored as UTC unchanged (no double conversion)."""
    from app.api.routes.api.energy_contracts import _localize_effective_from
    aware = datetime(2026, 6, 25, 0, 0, 0, tzinfo=UTC)
    utc_result = _localize_effective_from(aware)
    assert utc_result == aware


def test_localize_none_effective_from():
    """None effective_from must return current UTC time (as before)."""
    from app.api.routes.api.energy_contracts import _localize_effective_from
    before = datetime.now(UTC)
    result = _localize_effective_from(None)
    after = datetime.now(UTC)
    assert result.tzinfo is not None
    assert before <= result <= after
