from datetime import date

from django.core.exceptions import ValidationError
from django.core.paginator import Paginator
from django.db import models
from modelcluster.contrib.taggit import ClusterTaggableManager
from modelcluster.fields import ParentalKey
from taggit.models import TaggedItemBase

from wagtail.admin.panels import FieldPanel, InlinePanel, MultiFieldPanel
from wagtail.api import APIField
from wagtail.contrib.forms.models import AbstractForm, AbstractFormField
from wagtail.fields import RichTextField
from wagtail.images.api.fields import ImageRenditionField
from wagtail.models import Orderable, Page
from wagtail.search import index

# ABSTRACT MODELS
# =============================


class AbstractLinkFields(models.Model):
    link_external = models.URLField("External link", blank=True)
    link_page = models.ForeignKey(
        "wagtailcore.Page",
        null=True,
        blank=True,
        related_name="+",
        on_delete=models.CASCADE,
    )
    link_document = models.ForeignKey(
        "wagtaildocs.Document",
        null=True,
        blank=True,
        related_name="+",
        on_delete=models.CASCADE,
    )

    @property
    def link(self):
        if self.link_page:
            return self.link_page.url
        elif self.link_document:
            return self.link_document.url
        else:
            return self.link_external

    def clean(self):
        if (
            self.link_page is None
            and self.link_document is None
            and not self.link_external
        ):
            raise ValidationError(
                "You must provide a related page, related document or an external URL"
            )

    api_fields = ("link",)

    panels = [
        FieldPanel("link_external"),
        FieldPanel("link_page"),
        FieldPanel("link_document"),
    ]

    class Meta:
        abstract = True


class AbstractRelatedLink(AbstractLinkFields):
    title = models.CharField(max_length=255, help_text="Link title")

    api_fields = ("title",) + AbstractLinkFields.api_fields

    panels = [
        FieldPanel("title"),
        MultiFieldPanel(AbstractLinkFields.panels, "Link"),
    ]

    class Meta:
        abstract = True


class AbstractCarouselItem(AbstractLinkFields):
    image = models.ForeignKey(
        "wagtailimages.Image",
        null=True,
        blank=True,
        on_delete=models.SET_NULL,
        related_name="+",
    )
    embed_url = models.URLField("Embed URL", blank=True)
    caption = models.CharField(max_length=255, blank=True)

    api_fields = (
        "image",
        "embed_url",
        "caption",
    ) + AbstractLinkFields.api_fields

    panels = [
        FieldPanel("image"),
        FieldPanel("embed_url"),
        FieldPanel("caption"),
        MultiFieldPanel(AbstractLinkFields.panels, "Link"),
    ]

    class Meta:
        abstract = True


class ContactFieldsMixin(models.Model):
    telephone = models.CharField(max_length=20, blank=True)
    email = models.EmailField(blank=True)
    address_1 = models.CharField(max_length=255, blank=True)
    address_2 = models.CharField(max_length=255, blank=True)
    city = models.CharField(max_length=255, blank=True)
    country = models.CharField(max_length=255, blank=True)
    post_code = models.CharField(max_length=10, blank=True)

    api_fields = (
        "telephone",
        "email",
        "address_1",
        "address_2",
        "city",
        "country",
        "post_code",
    )

    panels = [
        FieldPanel("telephone"),
        FieldPanel("email"),
        FieldPanel("address_1"),
        FieldPanel("address_2"),
        FieldPanel("city"),
        FieldPanel("country"),
        FieldPanel("post_code"),
    ]

    class Meta:
        abstract = True


# PAGE MODELS
# =============================

# Home page


class HomePage(Page):
    page_ptr = models.OneToOneField(
        Page, parent_link=True, related_name="+", on_delete=models.CASCADE
    )
    body = RichTextField(blank=True)

    api_fields = (
        "body",
        "carousel_items",
        "related_links",
    )

    search_fields = Page.search_fields + [
        index.SearchField("body"),
    ]

    class Meta:
        verbose_name = "homepage"


class HomePageCarouselItem(Orderable, AbstractCarouselItem):
    page = ParentalKey(
        "HomePage", related_name="carousel_items", on_delete=models.CASCADE
    )


class HomePageRelatedLink(Orderable, AbstractRelatedLink):
    page = ParentalKey(
        "HomePage", related_name="related_links", on_delete=models.CASCADE
    )


HomePage.content_panels = Page.content_panels + [
    FieldPanel("body"),
    InlinePanel("carousel_items", label="carousel item"),
    InlinePanel("related_links", label="related link"),
]


# Standard pages


class StandardPage(Page):
    page_ptr = models.OneToOneField(
        Page, parent_link=True, related_name="+", on_delete=models.CASCADE
    )
    intro = RichTextField(blank=True)
    body = RichTextField(blank=True)
    feed_image = models.ForeignKey(
        "wagtailimages.Image",
        null=True,
        blank=True,
        on_delete=models.SET_NULL,
        related_name="+",
    )

    api_fields = (
        "intro",
        "body",
        "feed_image",
        "carousel_items",
        "related_links",
    )

    search_fields = Page.search_fields + [
        index.SearchField("intro"),
        index.SearchField("body"),
    ]


class StandardPageCarouselItem(Orderable, AbstractCarouselItem):
    page = ParentalKey(
        "StandardPage", related_name="carousel_items", on_delete=models.CASCADE
    )


class StandardPageRelatedLink(Orderable, AbstractRelatedLink):
    page = ParentalKey(
        "StandardPage", related_name="related_links", on_delete=models.CASCADE
    )


StandardPage.content_panels = Page.content_panels + [
    FieldPanel("intro"),
    InlinePanel("carousel_items", label="carousel item"),
    FieldPanel("body"),
    InlinePanel("related_links", label="related link"),
]


StandardPage.promote_panels = [
    MultiFieldPanel(Page.promote_panels, "Common page configuration"),
    FieldPanel("feed_image"),
]


class StandardIndexPage(Page):
    page_ptr = models.OneToOneField(
        Page, parent_link=True, related_name="+", on_delete=models.CASCADE
    )
    intro = RichTextField(blank=True)
    feed_image = models.ForeignKey(
        "wagtailimages.Image",
        null=True,
        blank=True,
        on_delete=models.SET_NULL,
        related_name="+",
    )

    api_fields = (
        "intro",
        "feed_image",
        "related_links",
    )

    search_fields = Page.search_fields + [
        index.SearchField("intro"),
    ]


class StandardIndexPageRelatedLink(Orderable, AbstractRelatedLink):
    page = ParentalKey(
        "StandardIndexPage", related_name="related_links", on_delete=models.CASCADE
    )


StandardIndexPage.content_panels = Page.content_panels + [
    FieldPanel("intro"),
    InlinePanel("related_links", label="related link"),
]


StandardIndexPage.promote_panels = [
    MultiFieldPanel(Page.promote_panels, "Common page configuration"),
    FieldPanel("feed_image"),
]


# Blog pages


class BlogEntryPage(Page):
    page_ptr = models.OneToOneField(
        Page, parent_link=True, related_name="+", on_delete=models.CASCADE
    )
    body = RichTextField()
    tags = ClusterTaggableManager(through="BlogEntryPageTag", blank=True)
    date = models.DateField("Post date")
    feed_image = models.ForeignKey(
        "wagtailimages.Image",
        null=True,
        blank=True,
        on_delete=models.SET_NULL,
        related_name="+",
    )

    api_fields = (
        APIField("body"),
        APIField("tags"),
        APIField("date"),
        APIField("feed_image"),
        APIField(
            "feed_image_thumbnail",
            serializer=ImageRenditionField("fill-300x300", source="feed_image"),
        ),
        APIField("carousel_items"),
        APIField("related_links"),
    )

    search_fields = Page.search_fields + [
        index.SearchField("body"),
    ]

    def get_blog_index(self):
        # Find closest ancestor which is a blog index
        return BlogIndexPage.ancestor_of(self).last()


class BlogEntryPageCarouselItem(Orderable, AbstractCarouselItem):
    page = ParentalKey(
        "BlogEntryPage", related_name="carousel_items", on_delete=models.CASCADE
    )


class BlogEntryPageRelatedLink(Orderable, AbstractRelatedLink):
    page = ParentalKey(
        "BlogEntryPage", related_name="related_links", on_delete=models.CASCADE
    )


class BlogEntryPageTag(TaggedItemBase):
    content_object = ParentalKey(
        "BlogEntryPage", related_name="tagged_items", on_delete=models.CASCADE
    )


BlogEntryPage.content_panels = Page.content_panels + [
    FieldPanel("date"),
    FieldPanel("body"),
    InlinePanel("carousel_items", label="carousel item"),
    InlinePanel("related_links", label="related link"),
]


BlogEntryPage.promote_panels = [
    MultiFieldPanel(Page.promote_panels, "Common page configuration"),
    FieldPanel("feed_image"),
    FieldPanel("tags"),
]


class BlogIndexPage(Page):
    page_ptr = models.OneToOneField(
        Page, parent_link=True, related_name="+", on_delete=models.CASCADE
    )
    intro = RichTextField(blank=True)

    api_fields = (
        "intro",
        "related_links",
    )

    search_fields = Page.search_fields + [
        index.SearchField("intro"),
    ]

    def get_blog_entries(self):
        # Get list of live blog pages that are descendants of this page
        entries = BlogEntryPage.objects.descendant_of(self).live()

        # Order by most recent date first
        entries = entries.order_by("-date")

        return entries

    def get_context(self, request):
        # Get blog entries
        entries = self.get_blog_entries()

        # Filter by tag
        tag = request.GET.get("tag")
        if tag:
            entries = entries.filter(tags__name=tag)

        paginator = Paginator(entries, per_page=10)
        entries = paginator.get_page(request.GET.get("page"))

        # Update template context
        context = super().get_context(request)
        context["entries"] = entries
        return context


class BlogIndexPageRelatedLink(Orderable, AbstractRelatedLink):
    page = ParentalKey(
        "BlogIndexPage", related_name="related_links", on_delete=models.CASCADE
    )


BlogIndexPage.content_panels = Page.content_panels + [
    FieldPanel("intro"),
    InlinePanel("related_links", label="related link"),
]


# Events pages


class EventPage(Page):
    page_ptr = models.OneToOneField(
        Page, parent_link=True, related_name="+", on_delete=models.CASCADE
    )
    AUDIENCE_CHOICES = (
        ("public", "Public"),
        ("private", "Private"),
    )

    date_from = models.DateField("Start date")
    date_to = models.DateField(
        "End date",
        null=True,
        blank=True,
        help_text="Not required if event is on a single day",
    )
    time_from = models.TimeField("Start time", null=True, blank=True)
    time_to = models.TimeField("End time", null=True, blank=True)
    audience = models.CharField(max_length=255, choices=AUDIENCE_CHOICES)
    location = models.CharField(max_length=255)
    body = RichTextField(blank=True)
    cost = models.CharField(max_length=255)
    signup_link = models.URLField(blank=True)
    feed_image = models.ForeignKey(
        "wagtailimages.Image",
        null=True,
        blank=True,
        on_delete=models.SET_NULL,
        related_name="+",
    )

    api_fields = (
        "date_from",
        "date_to",
        "time_from",
        "time_to",
        "audience",
        "location",
        "body",
        "cost",
        "signup_link",
        "feed_image",
        "carousel_items",
        "related_links",
        "speakers",
    )

    search_fields = Page.search_fields + [
        index.SearchField("get_audience_display"),
        index.SearchField("location"),
        index.SearchField("body"),
    ]

    def get_event_index(self):
        # Find closest ancestor which is an event index
        return EventIndexPage.objects.ancestor_of(self).last()


class EventPageCarouselItem(Orderable, AbstractCarouselItem):
    page = ParentalKey(
        "EventPage", related_name="carousel_items", on_delete=models.CASCADE
    )


class EventPageRelatedLink(Orderable, AbstractRelatedLink):
    page = ParentalKey(
        "EventPage", related_name="related_links", on_delete=models.CASCADE
    )


class EventPageSpeaker(Orderable, AbstractLinkFields):
    page = ParentalKey("EventPage", related_name="speakers", on_delete=models.CASCADE)
    first_name = models.CharField("Name", max_length=255, blank=True)
    last_name = models.CharField("Surname", max_length=255, blank=True)
    image = models.ForeignKey(
        "wagtailimages.Image",
        null=True,
        blank=True,
        on_delete=models.SET_NULL,
        related_name="+",
    )

    api_fields = (
        "first_name",
        "last_name",
        "image",
    )

    panels = [
        FieldPanel("first_name"),
        FieldPanel("last_name"),
        FieldPanel("image"),
        MultiFieldPanel(AbstractLinkFields.panels, "Link"),
    ]


EventPage.content_panels = Page.content_panels + [
    FieldPanel("date_from"),
    FieldPanel("date_to"),
    FieldPanel("time_from"),
    FieldPanel("time_to"),
    FieldPanel("location"),
    FieldPanel("audience"),
    FieldPanel("cost"),
    FieldPanel("signup_link"),
    InlinePanel("carousel_items", label="carousel item"),
    FieldPanel("body"),
    InlinePanel("speakers", label="speaker"),
    InlinePanel("related_links", label="related link"),
]


EventPage.promote_panels = [
    MultiFieldPanel(Page.promote_panels, "Common page configuration"),
    FieldPanel("feed_image"),
]


class EventIndexPage(Page):
    page_ptr = models.OneToOneField(
        Page, parent_link=True, related_name="+", on_delete=models.CASCADE
    )
    intro = RichTextField(blank=True)

    api_fields = (
        "intro",
        "related_links",
    )

    search_fields = Page.search_fields + [
        index.SearchField("intro"),
    ]

    def get_events(self):
        # Get list of live event pages that are descendants of this page
        events = EventPage.objects.descendant_of(self).live()

        # Filter events list to get ones that are either
        # running now or start in the future
        events = events.filter(date_from__gte=date.today())

        # Order by date
        events = events.order_by("date_from")

        return events


class EventIndexPageRelatedLink(Orderable, AbstractRelatedLink):
    page = ParentalKey(
        "EventIndexPage", related_name="related_links", on_delete=models.CASCADE
    )


EventIndexPage.content_panels = Page.content_panels + [
    FieldPanel("intro"),
    InlinePanel("related_links", label="related link"),
]


# Person page


class PersonPage(Page, ContactFieldsMixin):
    page_ptr = models.OneToOneField(
        Page, parent_link=True, related_name="+", on_delete=models.CASCADE
    )
    first_name = models.CharField(max_length=255)
    last_name = models.CharField(max_length=255)
    intro = RichTextField(blank=True)
    biography = RichTextField(blank=True)
    image = models.ForeignKey(
        "wagtailimages.Image",
        null=True,
        blank=True,
        on_delete=models.SET_NULL,
        related_name="+",
    )
    feed_image = models.ForeignKey(
        "wagtailimages.Image",
        null=True,
        blank=True,
        on_delete=models.SET_NULL,
        related_name="+",
    )

    api_fields = (
        "first_name",
        "last_name",
        "intro",
        "biography",
        "image",
        "feed_image",
        "related_links",
    ) + ContactFieldsMixin.api_fields

    search_fields = Page.search_fields + [
        index.SearchField("first_name"),
        index.SearchField("last_name"),
        index.SearchField("intro"),
        index.SearchField("biography"),
    ]


class PersonPageRelatedLink(Orderable, AbstractRelatedLink):
    page = ParentalKey(
        "PersonPage", related_name="related_links", on_delete=models.CASCADE
    )


PersonPage.content_panels = Page.content_panels + [
    FieldPanel("first_name"),
    FieldPanel("last_name"),
    FieldPanel("intro"),
    FieldPanel("biography"),
    FieldPanel("image"),
    MultiFieldPanel(ContactFieldsMixin.panels, "Contact"),
    InlinePanel("related_links", label="related link"),
]


PersonPage.promote_panels = [
    MultiFieldPanel(Page.promote_panels, "Common page configuration"),
    FieldPanel("feed_image"),
]


# Contact page


class ContactPage(Page, ContactFieldsMixin):
    page_ptr = models.OneToOneField(
        Page, parent_link=True, related_name="+", on_delete=models.CASCADE
    )
    body = RichTextField(blank=True)
    feed_image = models.ForeignKey(
        "wagtailimages.Image",
        null=True,
        blank=True,
        on_delete=models.SET_NULL,
        related_name="+",
    )

    api_fields = (
        "body",
        "feed_image",
    ) + ContactFieldsMixin.api_fields

    search_fields = Page.search_fields + [
        index.SearchField("body"),
    ]


ContactPage.content_panels = Page.content_panels + [
    FieldPanel("body"),
    MultiFieldPanel(ContactFieldsMixin.panels, "Contact"),
]


ContactPage.promote_panels = [
    MultiFieldPanel(Page.promote_panels, "Common page configuration"),
    FieldPanel("feed_image"),
]


class FormField(AbstractFormField):
    page = ParentalKey("FormPage", related_name="form_fields", on_delete=models.CASCADE)


class FormPage(AbstractForm):
    page_ptr = models.OneToOneField(
        Page, parent_link=True, related_name="+", on_delete=models.CASCADE
    )
    api_fields = [APIField("form_fields")]
    content_panels = AbstractForm.content_panels + [
        InlinePanel("form_fields", label="form field")
    ]
