from typing import Collection

import dateutil.parser
import importlib

from abc import ABCMeta, abstractmethod

from platypush.context import get_plugin
from platypush.plugins import Plugin, action


class CalendarInterface:
    __metaclass__ = ABCMeta

    @abstractmethod
    def get_upcoming_events(self, max_results=10):
        raise NotImplementedError()


class CalendarPlugin(Plugin, CalendarInterface):
    """
    The CalendarPlugin allows you to keep track of multiple calendars (Google or
    iCal URLs) and get joined events from all of them.
    """

    def __init__(self, calendars: Collection[dict], *args, **kwargs):
        """
        :param calendars: List of calendars to be queried. Supported types so far: Google Calendar and iCal URLs.

        Example:

            .. code-block:: yaml

                calendars:
                    # Use the Google Calendar integration
                    - type: google.calendar

                    # Import the Facebook events calendar via iCal URL
                    - type: calendar.ical
                      url: https://www.facebook.com/ical/u.php?uid=USER_ID&key=FB_KEY

        """

        super().__init__(*args, **kwargs)
        self.calendars = []

        for calendar in calendars:
            cal_type = calendar.pop('type', None)
            if cal_type is None:
                self.logger.warning(
                    "Invalid calendar with no type specified: %s", calendar
                )
                continue

            try:
                # New `calendar.name` format
                cal_plugin = get_plugin(cal_type).__class__
            except Exception:
                # Legacy `platypush.plugins.calendar.name.CalendarNamePlugin` format
                module_name = '.'.join(cal_type.split('.')[:-1])
                class_name = cal_type.split('.')[-1]
                module = importlib.import_module(module_name)
                cal_plugin = getattr(module, class_name)

            self.calendars.append(cal_plugin(**calendar))

    @action
    def get_upcoming_events(self, max_results=10):
        """
        Get a list of upcoming events merging all the available calendars.

        :param max_results: Maximum number of results to be returned (default: 10)
        :type max_results: int

        :returns: platypush.message.Response -- Response object with the list of
            events in the Google calendar API format.

        Example::

            output = [
                {
                    "id": "123456abcdef",
                    "kind": "calendar#event",
                    "status": "confirmed",
                    "htmlLink": "...",
                    "created": "2018-06-01T01:23:45.000Z",
                    "updated": "2018-06-01T01:23:45.000Z",
                    "creator": {
                        "email": "...",
                        "displayName": "...",
                        "self": true
                    },
                    "organizer" {
                        "email": "...",
                        "displayName": "...",
                        "self": true
                    },
                    "start": {
                        "dateTime": "2018-06-02T10:00:00.000Z",
                    },
                    "end": {
                        "dateTime": "2018-06-02T12:00:00.000Z",
                    },
                },
                ...
            ]
        """

        events = []

        for calendar in self.calendars:
            try:
                cal_events = calendar.get_upcoming_events().output or []
                events.extend(cal_events)
            except Exception as e:
                self.logger.warning(
                    'Could not retrieve events from calendar %s: %s', calendar, e
                )

        events = sorted(
            events,
            key=lambda event: dateutil.parser.parse(
                event['start']['dateTime']
                if 'dateTime' in event['start']
                else event['start']['date'] + 'T00:00:00+00:00'
            ),
        )[:max_results]

        return events


# vim:sw=4:ts=4:et: