import logging
from datetime import datetime

try:
  import _winreg as winreg
except ImportError:
  import winreg

try:
  import zoneinfo  # pragma: no cover
except ImportError:
  from backports import zoneinfo  # pragma: no cover

from .windows_tz import win_tz
from . import utils

_cache_tz = None
_cache_tz_name = None

log = logging.getLogger("tzlocal")


def valuestodict(key):
  """Convert a registry key's values to a dictionary."""
  result = {}
  size = winreg.QueryInfoKey(key)[1]
  for i in range(size):
    data = winreg.EnumValue(key, i)
    result[data[0]] = data[1]
  return result


def _get_dst_info(tz):
  # Find the offset for when it doesn't have DST:
  dst_offset = std_offset = None
  has_dst = False
  year = datetime.now().year
  for dt in (datetime(year, 1, 1), datetime(year, 6, 1)):
    if tz.dst(dt).total_seconds() == 0.0:
      # OK, no DST during winter, get this offset
      std_offset = tz.utcoffset(dt).total_seconds()
    else:
      has_dst = True

  return has_dst, std_offset, dst_offset


def _get_localzone_name():
  # Windows is special. It has unique time zone names (in several
  # meanings of the word) available, but unfortunately, they can be
  # translated to the language of the operating system, so we need to
  # do a backwards lookup, by going through all time zones and see which
  # one matches.
  tzenv = utils._tz_name_from_env()
  if tzenv:
    return tzenv

  log.debug("Looking up time zone info from registry")
  handle = winreg.ConnectRegistry(None, winreg.HKEY_LOCAL_MACHINE)

  TZLOCALKEYNAME = r"SYSTEM\CurrentControlSet\Control\TimeZoneInformation"
  localtz = winreg.OpenKey(handle, TZLOCALKEYNAME)
  keyvalues = valuestodict(localtz)
  localtz.Close()

  if "TimeZoneKeyName" in keyvalues:
    # Windows 7 and later

    # For some reason this returns a string with loads of NUL bytes at
    # least on some systems. I don't know if this is a bug somewhere, I
    # just work around it.
    tzkeyname = keyvalues["TimeZoneKeyName"].split("\x00", 1)[0]
  else:
    # Don't support XP any longer
    raise LookupError("Can not find Windows timezone configuration")

  timezone = win_tz.get(tzkeyname)
  if timezone is None:
    # Nope, that didn't work. Try adding "Standard Time",
    # it seems to work a lot of times:
    timezone = win_tz.get(tzkeyname + " Standard Time")

  # Return what we have.
  if timezone is None:
    raise zoneinfo.ZoneInfoNotFoundError(tzkeyname)

  if keyvalues.get("DynamicDaylightTimeDisabled", 0) == 1:
    # DST is disabled, so don't return the timezone name,
    # instead return Etc/GMT+offset

    tz = zoneinfo.ZoneInfo(timezone)
    has_dst, std_offset, dst_offset = _get_dst_info(tz)
    if not has_dst:
      # The DST is turned off in the windows configuration,
      # but this timezone doesn't have DST so it doesn't matter
      return timezone

    if std_offset is None:
      raise zoneinfo.ZoneInfoNotFoundError(
          f"{tzkeyname} claims to not have a non-DST time!?"
      )

    if std_offset % 3600:
      # I can't convert this to an hourly offset
      raise zoneinfo.ZoneInfoNotFoundError(
          f"tzlocal can't support disabling DST in the {timezone} zone."
      )

    # This has whole hours as offset, return it as Etc/GMT
    return f"Etc/GMT{-std_offset//3600:+.0f}"

  return timezone


def get_localzone_name():
  """Get the zoneinfo timezone name that matches the Windows-configured timezone."""
  global _cache_tz_name
  if _cache_tz_name is None:
    _cache_tz_name = _get_localzone_name()

  return _cache_tz_name


def get_localzone():
  """Returns the zoneinfo-based tzinfo object that matches the Windows-configured timezone."""

  global _cache_tz
  if _cache_tz is None:
    _cache_tz = zoneinfo.ZoneInfo(get_localzone_name())

  if not utils._tz_name_from_env():
    # If the timezone does NOT come from a TZ environment variable,
    # verify that it's correct. If it's from the environment,
    # we accept it, this is so you can run tests with different timezones.
    utils.assert_tz_offset(_cache_tz, error=False)

  return _cache_tz


def reload_localzone():
  """Reload the cached localzone. You need to call this if the timezone has changed."""
  global _cache_tz
  global _cache_tz_name
  _cache_tz_name = _get_localzone_name()
  _cache_tz = zoneinfo.ZoneInfo(_cache_tz_name)
  utils.assert_tz_offset(_cache_tz, error=False)
  return _cache_tz


if __name__ == '__main__':
  res = get_localzone_name()
