"""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/79185519/which-timezones-are-equivalent

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 pprint import pprint
from typing import Callable, NamedTuple, Optional

from pytz import AmbiguousTimeError, NonExistentTimeError
from zoneinfo import ZoneInfo, available_timezones

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)
    dt += timedelta(
        hours=25
    )  # This must be big enough to be fast and small enough to identify the timeszones before it is the present year
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.
    """
    print(create_timezones, name)
    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: Optional[set[str]] = 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]
        print(f"{len(todo)} left to compute")
        print(len(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, tzs in offsets.items():
                lookup[offset] = generate_tree(
                    tzs=tzs, step=step, start=start + step, end=end, todo=todo
                )
            return start, lookup
        print(f"reached end with {len(tzs)} timezones - assuming they are equivalent.")
        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"
    print(f"The result is written to {file}.")
    print("lookup = ", end="")
    pprint(lookup)
    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 = ")
        pprint(lookup, stream=f)
        f.write("\n\n__all__ = ['lookup']\n")

    return lookup


__all__ = ["main"]

if __name__ == "__main__":
    from dateutil.tz import gettz
    from pytz import timezone
    from zoneinfo import ZoneInfo

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