"""This tests the range parameter for ics file.
see https://github.com/niccokunzmann/python-recurring-ical-events/issues/75
Description:  This parameter can be specified on a property that
    specifies a recurrence identifier.  The parameter specifies the
    effective range of recurrence instances that is specified by the
    property.  The effective range is from the recurrence identifier
    specified by the property.  If this parameter is not specified on
    an allowed property, then the default range is the single instance
    specified by the recurrence identifier value of the property.  The
    parameter value can only be "THISANDFUTURE" to indicate a range
    defined by the recurrence identifier and all subsequent instances.
    The value "THISANDPRIOR" is deprecated by this revision of
    iCalendar and MUST NOT be generated by applications.

    - https://www.rfc-editor.org/rfc/rfc5545.html#section-3.2.13
"""

from datetime import time
from datetime import timedelta as td
from typing import TYPE_CHECKING

import pytest

from recurring_ical_events import EventAdapter

if TYPE_CHECKING:
    from calendar import Calendar


@pytest.mark.parametrize(
    ("date", "summary"),
    [
        ("20240901", "ORIGINAL EVENT"),
        ("20240911", "ORIGINAL EVENT"),
        ("20240913", "MODIFIED EVENT"),
        ("20240914", "MODIFIED EVENT"),  # RDATE
        ("20240915", "MODIFIED EVENT"),  # Normal recurrence-id
        ("20240917", "MODIFIED EVENT"),
        ("20240919", "MODIFIED EVENT"),
        ("20240922", "EDITED EVENT"),
        ("20240924", "EDITED EVENT"),
        ("20240926", "EDITED EVENT"),
    ],
)
def test_issue_75_RANGE_AT_parameter(calendars, date, summary):
    events = calendars.issue_75_range_parameter.at(date)
    assert len(events) == 1, f"Expecting one event at {date}"
    event = events[0]
    assert str(event["SUMMARY"]) == summary


@pytest.mark.parametrize(
    ("start", "end", "summary", "total"),
    [
        ("20240901T000000Z", "20240911T235959Z", "ORIGINAL EVENT", 6),
        ("20240901T000000Z", "20240913T000000Z", "ORIGINAL EVENT", 6),
        ("20240901T000000Z", "20240913T235959Z", "MODIFIED EVENT", 7),
        ("20240901T000000Z", "20240914T235959Z", "MODIFIED EVENT", 8),  # RDATE
        (
            "20240901T000000Z",
            "20240915T235959Z",
            "MODIFIED EVENT",
            9,
        ),  # Normal recurrence-id
        ("20240901T000000Z", "20240917T235959Z", "MODIFIED EVENT", 10),
        ("20240901T000000Z", "20240919T235959Z", "MODIFIED EVENT", 11),
        ("20240901T000000Z", "20240921T235959Z", "MODIFIED EVENT", 11),
        ("20240901T000000Z", "20240922T000000Z", "MODIFIED EVENT", 11),
        ("20240901T000000Z", "20240922T235959Z", "EDITED EVENT", 12),
        ("20240901T000000Z", "20240923T000000Z", "EDITED EVENT", 12),
        ("20240901T000000Z", "20240923T235959Z", "EDITED EVENT", 12),
        ("20240901T000000Z", "20240924T235959Z", "EDITED EVENT", 13),
        ("20240901T000000Z", "20240925T235959Z", "EDITED EVENT", 13),
        (
            "20240913T000000Z",
            "20240922T000000Z",
            "MODIFIED EVENT",
            5,
        ),  # out of query bounds
        (
            "20240913T000000Z",
            "20240922T235959Z",
            "EDITED EVENT",
            6,
        ),  # out of query bounds
        (
            "20240924T000000Z",
            "20240925T235959Z",
            "EDITED EVENT",
            1,
        ),  # out of query bounds
    ],
)
def test_issue_75_RANGE_BETWEEN_parameter(calendars, start, end, summary, total):
    events = calendars.issue_75_range_parameter.between(start, end)
    assert (
        len(events) == total
    ), f"Expecting {total} events at range {start}, {end}, get {len(events)}"
    event = events[-1]
    assert str(event["SUMMARY"]) == summary


@pytest.mark.parametrize(
    ("date", "start", "end"),
    [
        # moved by 3 hours forward
        ((2024, 9, 13, 9), (9, 0), (16, 0)),  # The modification itself
        ((2024, 9, 17, 9), (9, 0), (16, 0)),  # The recurrence after this moved
        # moved by 2h22m backward
        ((2024, 9, 22, 14, 22), (14, 22), (16, 13)),  # The modification itself
        ((2024, 9, 24, 14, 22), (14, 22), (16, 13)),  # The recurrence after this moved
    ],
)
def test_the_length_of_modified_events(calendars, date, start, end):
    """There should be one event exactly starting and ending at these times."""
    events = calendars.issue_75_range_parameter.at(date)
    assert len(events) != 0, "The calculation could not find an event!"
    assert len(events) == 1, "Modify the test to yield one event only!"
    event = events[0]
    assert event["DTSTART"].dt.time() == time(*start)
    assert event["DTEND"].dt.time() == time(*end)


@pytest.mark.parametrize(
    ("calendar", "event_index", "expected_start_delta", "expected_end_delta"),
    [
        # no recurrence id means 0
        ("issue_62_moved_event", 1, td(0), td(0)),
        # we moved 31 -> 17; 31-17
        ("issue_62_moved_event", 0, td(0), td(14)),
        # we have a duration added on top
        ("one_event", 0, td(minutes=30), td(0)),
        # we move to a later date, +1 day
        ("same_event_recurring_at_same_time", 1, td(days=1, hours=1), td(0)),
        # we move to the front, so we should still add the duration
        ("same_event_recurring_at_same_time", 2, td(0), td(hours=1)),
        # we moved with the THISANDFUTURE
        (
            "issue_75_range_parameter",
            3,
            td(days=1, hours=2, minutes=22) + td(hours=1, minutes=51),
            td(0),
        ),
    ],
)
def test_span_extension(
    calendars, calendar, event_index, expected_start_delta, expected_end_delta
):
    """If we have an event that is moved with THISANDFUTURE,
    other events move, too.

    This requires us to extend the range which we query:
    - If an event moves forward, we need to extend the span to the back ...
    - If an event moves backward, we need to extend the span to the front ...
    ... in order to capture the recurrences from the rrule that would yield
    the occurrence.

    If the length is extended, we can shorten the span
    If the length is reduced, we have to extend the span

    This tests the adapter to yield the correct values for the given types
    of moves.

    We only have to extend the range for THISANDFUTURE events because
    we iterate over all modifications either way.
    TODO: However, for optimization, one could approach to create ranges that
    specify how to extend and contract the spans.

    This test has to test of types of recurrence id, start and end.
    - date
    - datetime without tzinfo
    - datetime with UTC
    - datetime with tzinfo other than UTC

    >.The default value type is DATE-TIME.  The value type can
      be set to a DATE value type.  This property MUST have the same
      value type as the "DTSTART" property contained within the
      recurring component.  Furthermore, this property MUST be specified
      as a date with local time if and only if the "DTSTART" property
      contained within the recurring component is specified as a date
      with local time.
      - https://www.rfc-editor.org/rfc/rfc5545.html#section-3.8.4.4

    moves must include:
    - time forward
    - time backward
    - several days forward
    - several days backward

    Assumptions
    -----------

    This test is for a rought estimate. We can extend the range by +1 day into each direction.
    This will allow us to capture everything.
    Future examples and tests may help us improve the situation by narrowing it further down.

    Safe:
    - move 1 h forward -> END: add 1 day for timezone + 1 day for timedelta without timezone involvement (round up)
    """
    cal = calendars.raw[calendar]
    event = list(cal.walk("VEVENT"))[event_index]
    adapter = EventAdapter(event)
    assert adapter.duration >= td(0)
    start_delta, end_delta = adapter.extend_query_span_by
    assert start_delta >= expected_start_delta
    assert end_delta >= expected_end_delta


def test_can_calculate_query_span_extension_on_all_events(calendars, calendar_name):
    """Check that the calclulation succeeds."""
    for i, event in enumerate(calendars.raw[calendar_name].walk("VEVENT")):
        adapter = EventAdapter(event)
        start_delta, end_delta = adapter.extend_query_span_by
        message = f"{calendar_name}.VEVENT[{i}]"
        assert isinstance(start_delta, td), message
        assert isinstance(end_delta, td), message
        assert start_delta >= td(0), message
        assert end_delta >= td(0), message


def test_deletion_of_THISANDFUTURE_by_SEQUENCE():
    """We need to make sure that the components we have only work on what is actual."""
    pytest.skip("TODO")


def test_RDATE_with_PERIOD():
    """When an RDATE has a PERIOD, we can assume that that defines the new length."""
    pytest.skip("TODO")


@pytest.mark.parametrize(
    ("calendar_name", "event_index", "delta"),
    [
        ("one_event", 0, td(0)),
        ("same_event_recurring_at_same_time", 0, td(0)),
        ("issue_75_range_parameter", 1, td(hours=-3)),
        ("issue_75_range_parameter", 3, td(days=1, hours=2, minutes=22)),
    ],
)
def test_move_by_time(calendars, calendar_name, event_index, delta):
    """Check the moving of events."""
    cal: Calendar = calendars.raw[calendar_name]
    event = list(cal.walk("VEVENT"))[event_index]
    adapter = EventAdapter(event)
    assert adapter.move_recurrences_by == delta


# TODO: Test event with DTSTART = DATE - does it occur properly as it is
#       one day long, I believe. Loot at the RFC 5545.
