"""Timezone detection utilities for aioesphomeapi."""

from __future__ import annotations

import asyncio
from functools import cache, lru_cache
from importlib import resources
import logging

import tzlocal

from .singleton import singleton

_LOGGER = logging.getLogger(__name__)


def _load_tzdata(iana_key: str) -> bytes | None:
    """Load timezone data from tzdata package."""
    if not iana_key:
        return None
    try:
        package_loc, resource = iana_key.rsplit("/", 1)
    except ValueError:
        # Handle top-level timezone entries like "UTC", "GMT"
        package = "tzdata.zoneinfo"
        resource = iana_key
    else:
        package = "tzdata.zoneinfo." + package_loc.replace("/", ".")

    try:
        return (resources.files(package) / resource).read_bytes()
    except (FileNotFoundError, ModuleNotFoundError, IsADirectoryError):
        return None


def _extract_tz_string(tzfile: bytes) -> str:
    """Extract POSIX TZ string from tzdata file."""
    try:
        return tzfile.split(b"\n")[-2].decode()
    except (IndexError, UnicodeDecodeError):
        _LOGGER.exception("Could not determine TZ string from tzfile")
        return ""


@cache
def _get_local_timezone() -> str:
    """Get the local timezone as a POSIX TZ string (synchronous, cached).

    Returns a POSIX TZ string like 'CST6CDT,M3.2.0,M11.1.0' for America/Chicago.
    Returns empty string if timezone cannot be determined.

    This function is cached since the timezone doesn't change during runtime.
    This matches the implementation in ESPHome's time component.
    """
    try:
        # Use tzlocal to get the IANA timezone key, same as ESPHome
        iana_key: str | None = tzlocal.get_localzone_name()
        if iana_key is None:
            return ""

        # Load timezone data from tzdata package
        tzfile = _load_tzdata(iana_key)
        if tzfile is None:
            # Not an IANA key, probably already a TZ string
            return iana_key

        # Extract POSIX TZ string from tzdata file
        return _extract_tz_string(tzfile)
    except Exception:
        _LOGGER.exception("Failed to detect timezone")
        return ""


@lru_cache(maxsize=64)
def iana_to_posix_tz(iana_key: str) -> str:
    """Convert IANA timezone key to POSIX TZ string.

    Args:
        iana_key: IANA timezone key like 'America/Chicago'

    Returns:
        POSIX TZ string like 'CST6CDT,M3.2.0,M11.1.0'
        Returns empty string if conversion fails.

    """
    if (tzfile := _load_tzdata(iana_key)) is None:
        # Not an IANA key, return empty string
        return ""
    # Extract POSIX TZ string from tzdata file
    return _extract_tz_string(tzfile)


@singleton("local_timezone")
async def get_local_timezone() -> str:
    """Get the local timezone as a POSIX TZ string (async version).

    Returns a POSIX TZ string like 'CST6CDT,M3.2.0,M11.1.0' for America/Chicago.
    Returns empty string if timezone cannot be determined.

    This function runs in an executor to avoid blocking I/O and is cached
    using the singleton decorator.
    """
    loop = asyncio.get_running_loop()
    return await loop.run_in_executor(None, _get_local_timezone)


async def get_timezone(iana_key: str | None) -> str:
    """Get timezone as POSIX TZ string from IANA key or detect local.

    Args:
        iana_key: Optional IANA timezone key like 'America/Chicago'.
                  If None, detects local timezone.

    Returns:
        POSIX TZ string like 'CST6CDT,M3.2.0,M11.1.0'
        Returns empty string if timezone cannot be determined.

    """
    if not iana_key:
        return await get_local_timezone()
    loop = asyncio.get_running_loop()
    return await loop.run_in_executor(None, iana_to_posix_tz, iana_key)
