"""Support for ics parser."""

import re
from datetime import date, datetime, timedelta
from typing import Optional, Union

from arrow import Arrow, get as arrowget
from ics import Calendar

from ..filter import Filter
from ..icalendarparser import ICalendarParser
from ..parserevent import ParserEvent
from ..utility import compare_event_dates


class ParserICS(ICalendarParser):
    """Class to provide parser using ics module."""

    def __init__(self):
        """Construct ParserICS."""
        self._re_method = re.compile("^METHOD:.*$", flags=re.MULTILINE)
        self._calendar = None
        self._filter = Filter("", "")

    def set_content(self, content: str):
        """Parse content into a calendar object.

        This must be called at least once before get_event_list or
        get_current_event.
        :param content is the calendar data
        :type content str
        """
        self._calendar = Calendar(re.sub(self._re_method, "", content))

    def set_filter(self, filt: Filter):
        """Set a Filter object to filter events.

        :param filt: The Filter object
        :type exclude: Filter
        """
        self._filter = filt

    def get_event_list(
        self, start, end, include_all_day: bool, offset_hours: int = 0
    ) -> list[ParserEvent]:
        """Get a list of events.

        Gets the events from start to end, including or excluding all day
        events.
        :param start the earliest start time of events to return
        :type datetime
        :param end the latest start time of events to return
        :type datetime
        :param include_all_day if true, all day events will be included.
        :type boolean
        :param offset_hours the number of hours to offset the event
        :type offset_hours int
        :returns a list of events, or an empty list
        :rtype list[ParserEvent]
        """
        event_list: list[ParserEvent] = []

        if self._calendar is not None:
            # ics 0.8 takes datetime not Arrow objects
            # ar_start = start
            # ar_end = end
            ar_start = arrowget(start - timedelta(hours=offset_hours))
            ar_end = arrowget(end - timedelta(hours=offset_hours))

            for event in self._calendar.timeline.included(ar_start, ar_end):
                if event.all_day and not include_all_day:
                    continue
                summary: str = ""
                # ics 0.8 uses 'summary' reliably, older versions use 'name'
                # if hasattr(event, "summary"):
                #    summary = event.summary
                # elif hasattr(event, "name"):
                summary = event.name
                rrule = None
                for extra in event.extra:
                    if extra.name == "RRULE":
                        rrule = extra.value
                calendar_event: ParserEvent = ParserEvent(
                    uid=event.uid,
                    summary=summary,
                    start=ParserICS.get_date(
                        event.begin, event.all_day, offset_hours
                    ),
                    end=ParserICS.get_date(
                        event.end, event.all_day, offset_hours
                    ),
                    location=event.location,
                    description=event.description,
                    rrule=rrule,
                    recurrence_id=(
                        ParserICS.get_date(
                            event.begin, event.all_day, offset_hours
                        )
                        if rrule
                        else None
                    ),
                )
                if self._filter.filter_event(calendar_event):
                    event_list.append(calendar_event)

        return event_list

    def get_current_event(  # noqa: $701
        self,
        include_all_day: bool,
        now: datetime,
        days: int,
        offset_hours: int = 0,
    ) -> Optional[ParserEvent]:
        """Get the current or next event.

        Gets the current event, or the next upcoming event with in the
        specified number of days, if there is no current event.
        :param include_all_day if true, all day events will be included.
        :type boolean
        :param now the current date and time
        :type datetime
        :param days the number of days to check for an upcoming event
        :type int
        :param offset_hours the number of hours to offset the event
        :type int
        :returns a ParserEvent or None
        """
        if self._calendar is None:
            return None

        temp_event = None
        now = now - timedelta(offset_hours)
        end = now + timedelta(days=days)
        for event in self._calendar.timeline.included(
            arrowget(now), arrowget(end)
        ):
            if event.all_day and not include_all_day:
                continue

            if not self._filter.filter(event.name, event.description):
                continue

            if temp_event is None or compare_event_dates(
                now,
                temp_event.end,
                temp_event.begin,
                temp_event.all_day,
                event.end,
                event.begin,
                event.all_day,
            ):
                temp_event = event

        if temp_event is None:
            return None
        # if hasattr(event, "summary"):
        # summary = temp_event.summary
        # elif hasattr(event, "name"):
        summary = temp_event.name
        rrule = None
        for extra in temp_event.extra:
            if extra.name == "RRULE":
                rrule = extra.value
        return ParserEvent(
            uid=temp_event.uid,
            summary=summary,
            start=ParserICS.get_date(
                temp_event.begin, temp_event.all_day, offset_hours
            ),
            end=ParserICS.get_date(
                temp_event.end, temp_event.all_day, offset_hours
            ),
            location=temp_event.location,
            description=temp_event.description,
            rrule=rrule,
            recurrence_id=(
                ParserICS.get_date(
                    temp_event.begin, temp_event.all_day, offset_hours
                )
                if rrule
                else None
            ),
        )

    @staticmethod
    def get_date(
        arw: Arrow, is_all_day: bool, offset_hours: int
    ) -> Union[datetime, date]:
        """Get datetime.

        :param arw The arrow object representing the date.
        :type Arrow
        :param is_all_day If true, the returned datetime will have the time
        component set to 0.
        :type: bool
        :param offset_hours the number of hours to offset the event
        :type int
        :returns The datetime.
        :rtype datetime
        """
        # if isinstance(arw, Arrow):
        if is_all_day:
            return arw.date()
        # else:
        # if arw.tzinfo is None or arw.tzinfo.utcoffset(arw) is None
        #     or is_all_day:
        #        arw = arw.astimezone()
        # if is_all_day:
        #    return arw.date()
        #
        arw = arw.shift(hours=offset_hours)

        return_value = arw.datetime
        if return_value.tzinfo is None:
            return_value = return_value.astimezone()
        return return_value
