"""This module helps identifying the timezone ids and where they differ.

The algorithm: We use the tzname and the utcoffset for each hour from
1970 - 2030.
We make a big map.
If they are equivalent, they are equivalent within the time that is mostly used.

You can regenerate the information from this module.

See also:
- https://stackoverflow.com/questions/79171631/how-do-i-determine-whether-a-zoneinfo-is-an-alias/79171734#79171734

Run this module:

    python -m icalendar.timezone.equivalent_timezone_ids

"""

from __future__ import annotations

from collections import defaultdict
from datetime import datetime, timedelta, tzinfo
from pathlib import Path
from typing import TYPE_CHECKING, NamedTuple
from zoneinfo import ZoneInfo, available_timezones

from pytz import AmbiguousTimeError, NonExistentTimeError

if TYPE_CHECKING:
    from collections.abc import Callable

START = datetime(1970, 1, 1)  # noqa: DTZ001
END = datetime(2020, 1, 1)  # noqa: DTZ001
DISTANCE_FROM_TIMEZONE_CHANGE = timedelta(hours=12)

DTS = []
dt = START
while dt <= END:
    DTS.append(dt)
    # This must be big enough to be fast and small enough to identify the timeszones
    # before it is the present year
    dt += timedelta(hours=25)
del dt


def main(
    create_timezones: list[Callable[[str], tzinfo]],
    name: str,
):
    """Generate a lookup table for timezone information if unknown timezones.

    We cannot create one lookup for all because they seem to be all equivalent
    if we mix timezone implementations.
    """
    unsorted_tzids = available_timezones()
    unsorted_tzids.remove("localtime")
    unsorted_tzids.remove("Factory")

    class TZ(NamedTuple):
        tz: tzinfo
        id: str

    tzs = [
        TZ(create_timezone(tzid), tzid)
        for create_timezone in create_timezones
        for tzid in unsorted_tzids
    ]

    def generate_tree(
        tzs: list[TZ],
        step: timedelta = timedelta(hours=1),
        start: datetime = START,
        end: datetime = END,
        todo: set[str] | None = None,
    ) -> tuple[datetime, dict[timedelta, set[str]]] | set[str]:  # should be recursive
        """Generate a lookup tree."""
        if todo is None:
            todo = [tz.id for tz in tzs]
        if len(tzs) == 0:
            raise ValueError("tzs cannot be empty")
        if len(tzs) == 1:
            todo.remove(tzs[0].id)
            return {tzs[0].id}
        while start < end:
            offsets: dict[timedelta, list[TZ]] = defaultdict(list)
            try:
                # if we are around a timezone change, we must move on
                # see https://github.com/collective/icalendar/issues/776
                around_tz_change = not all(
                    tz.tz.utcoffset(start)
                    == tz.tz.utcoffset(start - DISTANCE_FROM_TIMEZONE_CHANGE)
                    == tz.tz.utcoffset(start + DISTANCE_FROM_TIMEZONE_CHANGE)
                    for tz in tzs
                )
            except (NonExistentTimeError, AmbiguousTimeError):
                around_tz_change = True
            if around_tz_change:
                start += DISTANCE_FROM_TIMEZONE_CHANGE
                continue
            for tz in tzs:
                offsets[tz.tz.utcoffset(start)].append(tz)
            if len(offsets) == 1:
                start += step
                continue
            lookup = {}
            for offset, tz2 in offsets.items():
                lookup[offset] = generate_tree(
                    tzs=tz2, step=step, start=start + step, end=end, todo=todo
                )
            return start, lookup
        result = set()
        for tz in tzs:
            result.add(tz.id)
            todo.remove(tz.id)
        return result

    lookup = generate_tree(tzs, step=timedelta(hours=33))

    file = Path(__file__).parent / f"equivalent_timezone_ids_{name}.py"
    with file.open("w") as f:
        f.write(
            f"'''This file is automatically generated by {Path(__file__).name}'''\n"
        )
        f.write("import datetime\n\n")
        f.write("\nlookup = ")
        f.write("\n\n__all__ = ['lookup']\n")

    return lookup


__all__ = ["main"]

if __name__ == "__main__":
    from zoneinfo import ZoneInfo

    from dateutil.tz import gettz
    from pytz import timezone

    # add more timezone implementations if you like
    main(
        [ZoneInfo, timezone, gettz],
        "result",
    )
