"""Timezone-aware datetime helpers.

All datetimes in BanGUI are stored and transmitted in UTC.
Conversion to the user's display timezone happens only at the presentation
layer (frontend). These utilities provide a consistent, safe foundation
for working with time throughout the backend.
"""

import datetime
import time


def utc_now() -> datetime.datetime:
    """Return the current UTC time as a timezone-aware :class:`datetime.datetime`.

    Returns:
        Current UTC datetime with ``tzinfo=datetime.UTC``.
    """
    return datetime.datetime.now(datetime.UTC)


def utc_from_timestamp(ts: float) -> datetime.datetime:
    """Convert a POSIX timestamp to a timezone-aware UTC datetime.

    Args:
        ts: POSIX timestamp (seconds since Unix epoch).

    Returns:
        Timezone-aware UTC :class:`datetime.datetime`.
    """
    return datetime.datetime.fromtimestamp(ts, tz=datetime.UTC)


def add_minutes(dt: datetime.datetime, minutes: int) -> datetime.datetime:
    """Return a new datetime that is *minutes* ahead of *dt*.

    Args:
        dt: The source datetime (must be timezone-aware).
        minutes: Number of minutes to add. May be negative.

    Returns:
        A new timezone-aware :class:`datetime.datetime`.
    """
    return dt + datetime.timedelta(minutes=minutes)


def is_expired(expires_at: datetime.datetime) -> bool:
    """Return ``True`` if *expires_at* is in the past relative to UTC now.

    Args:
        expires_at: The expiry timestamp to check (must be timezone-aware).

    Returns:
        ``True`` when the timestamp is past, ``False`` otherwise.
    """
    return utc_now() >= expires_at


def hours_ago(hours: int) -> datetime.datetime:
    """Return a timezone-aware UTC datetime *hours* before now.

    Args:
        hours: Number of hours to subtract from the current time.

    Returns:
        Timezone-aware UTC :class:`datetime.datetime`.
    """
    return utc_now() - datetime.timedelta(hours=hours)


def since_unix(range_: str) -> int:
    """Return the Unix timestamp for the start of a time-range window.

    Uses :func:`time.time` (always UTC epoch seconds on all platforms) to be
    consistent with how fail2ban stores ``timeofban`` values in its SQLite
    database. fail2ban records :func:`time.time()` values directly, so using
    a timezone-aware :func:`datetime.datetime.now`\\ ``(UTC).timestamp()``
    would theoretically produce the same result but using :func:`time.time`
    avoids any timezone-aware datetime pitfalls on misconfigured systems.

    A 60-second slack window is applied to accommodate clock drift and
    test seeding delays. This ensures consistent query windows across services
    (e.g., dashboard vs. history).

    Args:
        range_: One of the supported time-range presets (e.g., ``"24h"``).

    Returns:
        Unix timestamp (seconds since epoch) representing the start of the
        time window: *now − range_ − slack*.
    """
    from app.models._common import TIME_RANGE_SECONDS
    from app.utils.constants import TIME_RANGE_SLACK_SECONDS

    seconds: int = TIME_RANGE_SECONDS[range_]
    return int(time.time()) - seconds - TIME_RANGE_SLACK_SECONDS
