# #region AppTimezone [C:3] [TYPE Module] [SEMANTICS core,timezone,utilities,datetime]
# @BRIEF Application-level timezone utilities. Reads APP_TIMEZONE from env (default Europe/Moscow)
#         and provides helpers for converting UTC datetimes to the configured timezone.
# @RELATION CALLED_BY -> [ConfigManager]
# @RELATION DEPENDS_ON -> [EXT:method:GlobalSettings.app_timezone]
# @RATIONALE Centralised timezone resolution avoids scattering ZoneInfo() calls across the codebase.
#            All API responses should emit localised timestamps; internal storage remains UTC.
# @REJECTED Storing non-UTC in the database rejected — UTC is the canonical best practice for
#           persistence; timezone conversion is a presentation-layer concern.
from datetime import datetime
import os
from zoneinfo import ZoneInfo


# #region _get_default_tz_name [C:1] [TYPE Function]
# @BRIEF Read APP_TIMEZONE from env, fall back to Europe/Moscow.
# @PRE  Environment is loaded (dotenv or os.environ).
# @POST  Returns a valid IANA timezone string.
def _get_default_tz_name() -> str:
    return os.getenv("APP_TIMEZONE", "Europe/Moscow")
# #endregion _get_default_tz_name


# #region get_app_timezone [C:1] [TYPE Function]
# @BRIEF Return cached ZoneInfo for the configured application timezone.
# @SIDE_EFFECT  Reads os.environ on first call; result is cached.
# @POST  Returns a ZoneInfo instance matching APP_TIMEZONE env var.
_APP_TZ_CACHE: ZoneInfo | None = None

def get_app_timezone() -> ZoneInfo:
    global _APP_TZ_CACHE
    if _APP_TZ_CACHE is None:
        _APP_TZ_CACHE = ZoneInfo(_get_default_tz_name())
    return _APP_TZ_CACHE
# #endregion get_app_timezone


# #region invalidate_timezone_cache [C:1] [TYPE Function]
# @BRIEF Reset the cached ZoneInfo so the next get_app_timezone() call re-reads from env/DB.
# @POST  _APP_TZ_CACHE is set to None; subsequent get_app_timezone() will re-resolve.
# @RATIONALE  Called after PATCH /settings/consolidated updates app_timezone.
def invalidate_timezone_cache() -> None:
    global _APP_TZ_CACHE
    _APP_TZ_CACHE = None
# #endregion invalidate_timezone_cache


# #region validate_timezone [C:1] [TYPE Function]
# @BRIEF Validate that a timezone string is a known IANA timezone.
# @PRE  tz_name is a string.
# @POST  Returns True if ZoneInfo accepts the name, False otherwise.
def validate_timezone(tz_name: str) -> bool:
    try:
        ZoneInfo(tz_name)
        return True
    except (KeyError, TypeError):
        return False
# #endregion validate_timezone


# #region localize [C:1] [TYPE Function]
# @BRIEF Convert a timezone-aware or naive UTC datetime to the configured app timezone.
# @PRE  If dt is timezone-naive, it is assumed to be UTC.
# @POST  Returns a datetime with the app timezone attached (astimezone).
def localize(dt: datetime | None) -> datetime | None:
    if dt is None:
        return None
    if dt.tzinfo is None:
        dt = dt.replace(tzinfo=ZoneInfo("UTC"))
    return dt.astimezone(get_app_timezone())
# #endregion localize


# #region now [C:1] [TYPE Function]
# @BRIEF Get current time in the configured application timezone.
# @POST  Returns timezone-aware datetime in the app timezone.
def now() -> datetime:
    return datetime.now(get_app_timezone())
# #endregion now


# #region format_timezone_offset [C:1] [TYPE Function]
# @BRIEF Return the UTC offset string for the configured timezone (e.g. "+03:00").
# @POST  Returns string like "+03:00" or "+00:00".
def format_timezone_offset() -> str:
    offset = now().strftime("%z")
    if offset:
        return f"{offset[:3]}:{offset[3:]}"
    return "+00:00"
# #endregion format_timezone_offset
# #endregion AppTimezone
