"""Server local-timezone helpers.

All time is stored as UTC (no change).  This module provides a single,
monkeypatch-friendly entry point for converting UTC moments to the server's
local timezone — used for business-day boundary calculations (standing charges,
effective-from date interpretation, etc.).

Design goals
------------
- Zero third-party dependencies (no tzlocal, no pytz).
- Testable without depending on CI host timezone:
  monkeypatch ``local_tz`` to a fixed ``ZoneInfo`` in unit tests.
- DST-correct: conversions use ``astimezone(local_tz())`` per instant, so each
  moment is independently correct even during DST transitions.
- Consistent with the existing ``.astimezone()`` pattern already used in
  ``homeassistant_inbound.py`` and ``poo.py``.

Priority for resolving the local timezone
-----------------------------------------
1. ``TZ`` environment variable — ``ZoneInfo(os.environ["TZ"])``.
   Set ``TZ=Europe/Amsterdam`` in the deployment env for correct NL handling.
2. System local timezone fallback: ``datetime.now().astimezone().tzinfo``.
   This matches the behaviour callers already relied on implicitly.
"""

from __future__ import annotations

import os
from datetime import date, datetime, timezone
from typing import TYPE_CHECKING
from zoneinfo import ZoneInfo

if TYPE_CHECKING:
    from datetime import tzinfo


def local_tz() -> "tzinfo":
    """Return the server's local timezone.

    Resolution order:
    1. ``TZ`` environment variable (``ZoneInfo(TZ)``).  Set
       ``TZ=Europe/Amsterdam`` in production for correct NL/DST handling.
    2. System local timezone via ``datetime.now().astimezone().tzinfo``.

    **Monkeypatch this function in tests** to get deterministic timezone
    behaviour regardless of CI host configuration::

        monkeypatch.setattr("app.services.timezone.local_tz",
                            lambda: ZoneInfo("Europe/Amsterdam"))
    """
    tz_env = os.environ.get("TZ", "").strip()
    if tz_env:
        return ZoneInfo(tz_env)
    # System fallback — identical to the .astimezone() pattern already used
    # in homeassistant_inbound.py and poo.py.
    return datetime.now().astimezone().tzinfo  # type: ignore[return-value]


def to_local(dt: datetime) -> datetime:
    """Convert *dt* to the server local timezone.

    Handles three input cases:
    - timezone-aware (any zone): converted via ``astimezone``.
    - timezone-naive: assumed UTC (SQLite read-back convention), then converted.

    Returns a timezone-aware datetime in ``local_tz()``.
    """
    if dt.tzinfo is None:
        dt = dt.replace(tzinfo=timezone.utc)
    return dt.astimezone(local_tz())


def local_now() -> datetime:
    """Return the current moment as a timezone-aware local datetime."""
    return datetime.now(local_tz())


def local_date(dt: datetime) -> date:
    """Return the local calendar date for *dt*.

    Equivalent to ``to_local(dt).date()``.  A distinct helper to make
    call sites readable.
    """
    return to_local(dt).date()


def local_midnight_utc(d: date) -> datetime:
    """Return the UTC instant that corresponds to local midnight on *d*.

    Example (Europe/Amsterdam, CEST=UTC+2):
        local_midnight_utc(date(2026, 6, 25))
        → datetime(2026, 6, 24, 22, 0, 0, tzinfo=UTC)

    This is used to convert a local calendar date back to a UTC boundary, e.g.
    to compute the start/end of a local day for DB queries.
    """
    tz = local_tz()
    # Build a naive local datetime at midnight, then localize and convert to UTC.
    local_midnight = datetime(d.year, d.month, d.day, 0, 0, 0, tzinfo=tz)
    return local_midnight.astimezone(timezone.utc)
