import json
import logging
from datetime import date, timedelta

import arrow
from django.core.serializers.json import DjangoJSONEncoder
from django.core.validators import URLValidator, ValidationError
from django.db import models
from icalevents import icalevents
from wagtail.admin.panels import FieldPanel
from wagtail.models import Page
from wagtailmetadata.models import MetadataPageMixin

from .parser import process_event_list

logger = logging.getLogger(__name__)


def _convert_arrow_to_datetime(event):
    event["start"] = event["start"].datetime
    event["end"] = event["end"].datetime
    return event


class EventsJSONField(models.JSONField):
    """
    JSONField for lists of events which converts `begin` and `end` to datetime
    on load from DB.
    """

    def from_db_value(self, value, expression, connection):
        value = super().from_db_value(value, expression, connection)
        urlValidator = URLValidator()
        if value:
            for event in value:
                event["start"] = arrow.get(event.get("start")).datetime
                event["end"] = arrow.get(event["end"]).datetime
                try:
                    urlValidator(event.get("location"))
                    event["url"] = event.get("location")
                except ValidationError:
                    pass

        return value


class Calendar(models.Model):
    CURRENT_NUM = 6

    url = models.URLField()
    event_hash = models.CharField(max_length=256, null=True)
    last_update = models.DateTimeField(null=True)
    past_events = EventsJSONField(encoder=DjangoJSONEncoder, null=True)
    future_events = EventsJSONField(encoder=DjangoJSONEncoder, null=True)

    def current_events(self):
        return self.future_events[: self.CURRENT_NUM]

    def handle_event_list(self, event_list):
        event_list_hash = str(hash(str(event_list)))
        if event_list_hash != self.event_hash:
            past, future = process_event_list(event_list)
            self.past_events = past
            self.future_events = future
            self.event_hash = event_list_hash

        self.last_update = arrow.utcnow().datetime
        self.save()

    def update_source(self):
        event_list = icalevents.events(
            url=self.url,
            start=date.today() - timedelta(days=30),
            end=date.today() + timedelta(days=60),
        )
        self.handle_event_list(event_list)


class CalendarMixin(models.Model):
    """
    Mixin to be used in other models, like site settings, which adds relation
    to Calendar.
    """

    calendar_url = models.URLField(
        "URL kalendáře ve formátu iCal", blank=True, null=True
    )
    calendar = models.ForeignKey(
        Calendar, null=True, blank=True, on_delete=models.PROTECT
    )
    calendar_page = models.ForeignKey(
        "calendar_utils.CalendarPage",
        verbose_name="Stránka s kalendářem",
        on_delete=models.PROTECT,
        null=True,
        blank=True,
    )

    class Meta:
        abstract = True

    def get_fullcalendar_data(self) -> str:
        calendar_format_events = []

        for event in (
            self.calendar.past_events if self.calendar.past_events is not None else []
        ) + (
            self.calendar.future_events
            if self.calendar.future_events is not None
            else []
        ):
            parsed_event = {
                "allDay": event["all_day"],
                "start": event["start"].isoformat(),
                "end": event["end"].isoformat(),
            }

            if event["summary"] not in ("", None):
                parsed_event["title"] = event["summary"]

            if event["url"] not in ("", None):
                parsed_event["url"] = event["url"]

            if event["location"] not in ("", None):
                parsed_event["location"] = event["location"]

            if event["description"] not in ("", None):
                parsed_event["description"] = event["description"]

            calendar_format_events.append(parsed_event)

        return json.dumps(calendar_format_events)

    def save(self, *args, **kwargs):
        # create or update related Calendar
        if self.calendar_url:
            if self.calendar:
                if self.calendar.url != self.calendar_url:
                    self.calendar.url = self.calendar_url
                    self.calendar.save()
            else:
                self.calendar = Calendar.objects.create(url=self.calendar_url)

            try:
                self.calendar.update_source()
            except:
                logger.error(
                    "Calendar update failed for %s", self.calendar.url, exc_info=True
                )

        # delete related Calendar when URL is cleared
        if not self.calendar_url and self.calendar:
            self.calendar = None

        super().save(*args, **kwargs)


class CalendarPage(SubpageMixin, MetadataPageMixin, CalendarMixin, Page):
    """
    Page for displaying full calendar
    """

    calendar_url = models.URLField(
        "URL kalendáře ve formátu iCal", blank=False, null=True
    )

    ### PANELS

    content_panels = Page.content_panels + [
        FieldPanel("calendar_url"),
    ]

    ### RELATIONS

    parent_page_types = [
        "district.DistrictCenterPage",
        "district.DistrictHomePage",
        "elections2021.Elections2021CalendarPage",
        "senat_campaign.SenatCampaignHomePage",
        "uniweb.UniwebHomePage",
    ]
    subpage_types = []

    ### OTHERS

    class Meta:
        verbose_name = "Stránka s kalendářem"