"""User-timezone helpers.

All datetimes in the DB are stored as UTC. The helpers here bridge between
that UTC storage and the user's configured local timezone (IANA string in
the ``user_timezone`` setting). Use these anywhere the model or UI talks
in terms of "today", "tomorrow", or a bare calendar date — never
``date.today()`` or ``datetime.now(timezone.utc)`` inside a per-user flow.
"""
from __future__ import annotations

from datetime import date, datetime, timedelta
from zoneinfo import ZoneInfo, ZoneInfoNotFoundError

from scribe.services.settings import get_setting

# Day-rollover boundary for day-anchored "today" logic. The "day" flips at
# this local hour (not midnight) so the 00:00–04:00 local window still
# shows yesterday's content until the user "starts" the new day.
DAY_ROLLOVER_HOUR = 4


async def get_user_tz(user_id: int) -> ZoneInfo:
    """Return the user's IANA ``ZoneInfo``, falling back to UTC."""
    tz_str = await get_setting(user_id, "user_timezone") or "UTC"
    try:
        return ZoneInfo(tz_str)
    except (ZoneInfoNotFoundError, KeyError):
        return ZoneInfo("UTC")


async def user_today(user_id: int) -> date:
    """Return today's calendar date in the user's local timezone."""
    tz = await get_user_tz(user_id)
    return datetime.now(tz).date()


async def user_day_date(user_id: int) -> date:
    """Return the current "day" in the user's local timezone.

    The day flips at ``DAY_ROLLOVER_HOUR`` (4am local) rather than midnight,
    so the 00:00–04:00 local window still returns *yesterday* — a day-anchored
    view stays on yesterday until the user crosses the rollover.
    """
    tz = await get_user_tz(user_id)
    return (datetime.now(tz) - timedelta(hours=DAY_ROLLOVER_HOUR)).date()
