"""
Implementation of a L{TimeDriver} and a L{Scale} to convert floating-point
POSIX timestamps into timezone-aware datetimes.

@note: Although at runtime this module uses L{datetime.datetime} objects, its
    type hints use the U{datetype <https://pypi.org/project/datetype/>} library
    to ensure that all values have the correct type of tzinfo.  This driver
    will I{not} work with arbitrary L{datetime.timezone} zones, I{only}
    L{zoneinfo.ZoneInfo} zones, as IANA timezone identifiers are required for
    reliable long-term serialization across DST boundaries.
"""

from __future__ import annotations

from dataclasses import dataclass
from typing import Callable
from zoneinfo import ZoneInfo

from datetype import DateTime

from ..boundaries import (
    TimeDriver,
    Scale,
)


__all__ = [
    "guessLocalZone",
    "DateScale",
    "DateTimeDriver",
]

_PS_TZ_CMD = """\
powershell \
[\
Windows.Globalization.Calendar,\
Windows.Globalization,\
ContentType=WindowsRuntime\
]\
::New().GetTimeZone()\
"""

_guessedZone: ZoneInfo | None = None


def guessLocalZone() -> ZoneInfo:
    """
    Attempt to determine the IANA timezone identifier for the local system
    using a variety of heuristics.
    """
    global _guessedZone
    if _guessedZone is not None:
        return _guessedZone
    from os import name, readlink

    if name == "nt":
        from os import popen

        ianaID = popen(_PS_TZ_CMD).read().strip()
    else:
        path = readlink("/etc/localtime").split("/")
        ianaID = "/".join(path[path.index("zoneinfo") + 1 :])
    _guessedZone = ZoneInfo(ianaID)
    return _guessedZone


@dataclass
class DateScale:
    """
    A L{fritter.boundaries.Scale} that can scale between a trunk scheduler that
    uses physical time (floats) and a branch scheduler that uses civil time
    (dates and wall-clock times).
    """

    _zone: ZoneInfo
    _onlyPauseShift: bool = True

    def up(self, offset: float, time: DateTime[ZoneInfo]) -> float:
        """
        Translate C{time} from the branch time scale into the trunk time scale.
        """
        return time.timestamp() + offset

    def down(self, offset: float, time: float) -> DateTime[ZoneInfo]:
        """
        Translate C{time} from the trunk time scale into the branch time scale.
        """
        return DateTime.fromtimestamp(time - offset, self._zone)

    def shift(
        self, pauseTime: DateTime[ZoneInfo] | None, currentTime: float
    ) -> float:
        """
        Shift the current scale forward to incorporate pause breaks.
        """
        if self._onlyPauseShift and pauseTime is None:
            return 0.0
        return (
            currentTime
            if pauseTime is None
            else currentTime - pauseTime.timestamp()
        )


_DateScaleTypeCheck: type[Scale[DateTime[ZoneInfo], float, float]] = DateScale


@dataclass(frozen=True)
class DateTimeDriver:
    """
    Driver based on ZoneInfo-aware datetimes.

    @ivar driver: the L{TimeDriver} that this L{DateTimeDriver} is layered on
        top of, one which represents time as a POSIX timestamp as a L{float}.

    @ivar zone: the default timezone of this L{DateTimeDriver}, the one which
        will be used for results from L{now() <DateTimeDriver.now>}.  Note that
        L{reschedule(...) <DateTimeDriver.reschedule>} will still take inputs
        in any zone; this is just the default zone for outputs.
    """

    driver: TimeDriver[float]
    zone: ZoneInfo = ZoneInfo("Etc/UTC")

    def unschedule(self) -> None:
        "Implementation of L{TimeDriver.unschedule}"
        self.driver.unschedule()

    def reschedule(
        self, newTime: DateTime[ZoneInfo], work: Callable[[], None]
    ) -> None:
        "Implementation of L{TimeDriver.reschedule}"
        self.driver.reschedule(newTime.timestamp(), work)

    def now(self) -> DateTime[ZoneInfo]:
        "Implementation of L{TimeDriver.now}"
        timestamp = self.driver.now()
        return DateTime.fromtimestamp(timestamp, self.zone)


_DriverTypeCheck: type[TimeDriver[DateTime[ZoneInfo]]] = DateTimeDriver
