"""Tests for Free/Busy component."""

from __future__ import annotations

import datetime
import inspect

import pytest
from freezegun import freeze_time
from pydantic import ValidationError

from ical.calendar import Calendar
from ical.calendar_stream import IcsCalendarStream
from ical.timezone import IcsTimezoneInfo, Observance, Timezone
from ical.types import UtcOffset
from ical.types.recur import Frequency, Recur, Weekday, WeekdayValue
from ical.tzif.timezoneinfo import TimezoneInfoError

TEST_RECUR = Recur(
    freq=Frequency.YEARLY,
    by_month=[10],
    by_day=[WeekdayValue(Weekday.SUNDAY, occurrence=-1)],
    until=datetime.datetime(2006, 10, 29, 6, 0, 0),
)


def test_requires_subcompnent() -> None:
    """Test Timezone constructor."""
    with pytest.raises(ValidationError, match=r"At least one standard or daylight.*"):
        Timezone(tz_id="America/New_York")


def test_daylight() -> None:
    """Test a Timezone object with a daylight observance."""
    timezone = Timezone(
        tz_id="America/New_York",
        last_modified=datetime.datetime(2005, 8, 9, 5),
        daylight=[
            Observance(
                start=datetime.datetime(1967, 10, 29, 2, 0, 0, 0),
                tz_offset_to=UtcOffset(datetime.timedelta(hours=-4)),
                tz_offset_from=UtcOffset(datetime.timedelta(hours=-5)),
                tz_name=["edt"],
                rrule=TEST_RECUR,
            ),
        ],
    )
    assert len(timezone.daylight) == 1
    assert timezone.daylight[0].tz_name == ["edt"]

    tz_info = IcsTimezoneInfo.from_timezone(timezone)

    value = datetime.datetime(1967, 10, 29, 1, 59, 0, 0, tzinfo=tz_info)
    assert not tz_info.tzname(value)
    assert not tz_info.utcoffset(value)
    assert not tz_info.dst(value)

    value = datetime.datetime(1967, 10, 29, 2, 00, 0, 0, tzinfo=tz_info)
    assert tz_info.tzname(value) == "edt"
    assert tz_info.utcoffset(value) == datetime.timedelta(hours=-4)
    assert tz_info.dst(value) == datetime.timedelta(hours=1)


def test_timezone_observence_start_time_validation() -> None:
    """Verify that a start time must be in local time."""
    with pytest.raises(
        ValidationError, match=r".*Start time must be in local time format*"
    ):
        Observance(
            start=datetime.datetime(1967, 10, 29, 2, tzinfo=datetime.timezone.utc),
            tz_offset_to=UtcOffset(datetime.timedelta(hours=-5)),
            tz_offset_from=UtcOffset(datetime.timedelta(hours=-4)),
            tz_name=["est"],
            rrule=TEST_RECUR,
        )


@freeze_time("2022-08-22 12:30:00")
def test_from_tzif_timezoneinfo_with_dst() -> None:
    """Verify a timezone created from a tzif timezone info with DST information."""

    timezone = Timezone.from_tzif("America/New_York")

    calendar = Calendar()
    calendar.timezones.append(timezone)

    stream = IcsCalendarStream(calendars=[calendar])
    assert stream.ics() == inspect.cleandoc(
        """
       BEGIN:VCALENDAR
       BEGIN:VTIMEZONE
       DTSTAMP:20220822T123000
       TZID:America/New_York
       BEGIN:STANDARD
       DTSTART:20101107T020000
       TZOFFSETTO:-0500
       TZOFFSETFROM:-0400
       RRULE:FREQ=YEARLY;BYDAY=1SU;BYMONTH=11
       TZNAME:EST
       END:STANDARD
       BEGIN:DAYLIGHT
       DTSTART:20100314T020000
       TZOFFSETTO:-0400
       TZOFFSETFROM:-0500
       RRULE:FREQ=YEARLY;BYDAY=2SU;BYMONTH=3
       TZNAME:EDT
       END:DAYLIGHT
       END:VTIMEZONE
       END:VCALENDAR
    """
    )

    tz_info = IcsTimezoneInfo.from_timezone(timezone)

    value = datetime.datetime(2010, 11, 7, 1, 59, 0)
    assert tz_info.tzname(value) == "EDT"
    assert tz_info.utcoffset(value) == datetime.timedelta(hours=-4)
    assert tz_info.dst(value) == datetime.timedelta(hours=1)

    value = datetime.datetime(2010, 11, 7, 2, 0, 0)
    assert tz_info.tzname(value) == "EST"
    assert tz_info.utcoffset(value) == datetime.timedelta(hours=-5)
    assert tz_info.dst(value) == datetime.timedelta(hours=0)

    value = datetime.datetime(2011, 3, 13, 1, 59, 0)
    assert tz_info.tzname(value) == "EST"
    assert tz_info.utcoffset(value) == datetime.timedelta(hours=-5)
    assert tz_info.dst(value) == datetime.timedelta(hours=0)

    value = datetime.datetime(2011, 3, 14, 2, 0, 0)
    assert tz_info.tzname(value) == "EDT"
    assert tz_info.utcoffset(value) == datetime.timedelta(hours=-4)
    assert tz_info.dst(value) == datetime.timedelta(hours=1)


@freeze_time("2022-08-22 12:30:00")
def test_from_tzif_timezoneinfo_fixed_offset() -> None:
    """Verify a timezone created from a tzif timezone info with a fixed offset"""

    timezone = Timezone.from_tzif("Asia/Tokyo")

    calendar = Calendar()
    calendar.timezones.append(timezone)

    stream = IcsCalendarStream(calendars=[calendar])
    assert stream.ics() == inspect.cleandoc(
        """
       BEGIN:VCALENDAR
       BEGIN:VTIMEZONE
       DTSTAMP:20220822T123000
       TZID:Asia/Tokyo
       BEGIN:STANDARD
       DTSTART:20100101T000000
       TZOFFSETTO:0900
       TZOFFSETFROM:0900
       TZNAME:JST
       END:STANDARD
       END:VTIMEZONE
       END:VCALENDAR
    """
    )


def test_invalid_tzif_key() -> None:
    """Test creating a timezone object from tzif data that does not exist."""

    with pytest.raises(TimezoneInfoError, match=r"Unable to find timezone"):
        Timezone.from_tzif("invalid")
