from __future__ import annotations

from datetime import datetime, timedelta, timezone, tzinfo
from functools import lru_cache


@lru_cache(maxsize=1)
def get_windows_localzone() -> tzinfo:
    try:
        import tzlocal

        zone = tzlocal.get_localzone()
        if zone is not None:
            return zone
    except Exception:
        pass

    try:
        import win32timezone

        return win32timezone.TimeZoneInfo.local()
    except Exception:
        pass

    return datetime.now().astimezone().tzinfo or timezone.utc


@lru_cache(maxsize=1)
def get_windows_localzone_name() -> str:
    try:
        import tzlocal

        name = str(tzlocal.get_localzone_name() or "").strip()
        if name:
            return name
    except Exception:
        pass

    zone = get_windows_localzone()
    for attr in ("key", "zone"):
        value = str(getattr(zone, attr, "") or "").strip()
        if value:
            return value

    tz_name = str(datetime.now(zone).tzname() or "").strip()
    return tz_name or "UTC"


@lru_cache(maxsize=1)
def get_outlook_timezone_id() -> str:
    try:
        import pythoncom
        import win32com.client

        pythoncom.CoInitialize()
        try:
            outlook = win32com.client.Dispatch("Outlook.Application")
            time_zones = getattr(outlook, "TimeZones", None)
            current = getattr(time_zones, "CurrentTimeZone", None)
            tz_id = str(getattr(current, "ID", "") or "").strip()
            if tz_id:
                return tz_id
        finally:
            pythoncom.CoUninitialize()
    except Exception:
        pass
    return ""


@lru_cache(maxsize=1)
def get_outlook_timezone() -> tzinfo | None:
    tz_id = get_outlook_timezone_id()
    if not tz_id:
        return None
    try:
        import win32timezone

        return win32timezone.TimeZoneInfo(tz_id)
    except Exception:
        return None


def get_windows_now() -> datetime:
    return datetime.now(get_windows_localzone())


def localize_windows_datetime(value: datetime) -> datetime:
    zone = get_windows_localzone()
    if value.tzinfo is None:
        return value.replace(tzinfo=zone)
    return value.astimezone(zone)


def localize_calendar_match_datetime(value: datetime, *, provider_scope: str = "") -> datetime:
    local_zone = get_windows_localzone()
    if value.tzinfo is not None:
        return value.astimezone(local_zone)

    provider = str(provider_scope or "").strip().lower()
    source_zone = get_outlook_timezone() if provider == "outlook_only" else None
    if source_zone is None:
        source_zone = local_zone
    return value.replace(tzinfo=source_zone).astimezone(local_zone)


def to_windows_naive_datetime(value: datetime) -> datetime:
    return localize_windows_datetime(value).replace(tzinfo=None)


def windows_utc_offset(value: datetime | None = None) -> str:
    local_value = localize_windows_datetime(value or get_windows_now())
    offset = local_value.utcoffset() or timedelta(0)
    total = int(offset.total_seconds())
    sign = "+" if total >= 0 else "-"
    hours, remainder = divmod(abs(total), 3600)
    minutes = remainder // 60
    return f"{sign}{hours:02d}:{minutes:02d}"


def utc_now_iso_z() -> str:
    return datetime.now(timezone.utc).isoformat(timespec="seconds").replace("+00:00", "Z")