from django import forms
from django.core.exceptions import ValidationError
from django.db import models
from django.db.models import Min, Prefetch, Q, UniqueConstraint
from django.utils import timezone
from django.utils.html import mark_safe
from django.utils.translation import gettext_lazy as _
from grapple.helpers import register_query_field, register_singular_query_field
from grapple.models import (
    GraphQLBoolean,
    GraphQLCollection,
    GraphQLForeignKey,
    GraphQLImage,
    GraphQLRichText,
    GraphQLStreamfield,
    GraphQLString,
)
from modelcluster.fields import ParentalKey, ParentalManyToManyField
from modelcluster.models import ClusterableModel
from wagtail.admin.panels import (
    FieldPanel,
    FieldRowPanel,
    HelpPanel,
    InlinePanel,
    MultiFieldPanel,
    MultipleChooserPanel,
    TitleFieldPanel,
)
from wagtail.fields import RichTextField
from wagtail.models import Orderable, Page, PageManager, PageQuerySet
from wagtail.search import index
from wagtail.snippets.models import register_snippet
from wagtail_headless_preview.models import HeadlessMixin

from associations.widgets import AssociationChooserWidget
from dnscms.fields import CommonStreamField
from dnscms.options import ALL_PIGS
from dnscms.wordpress.models import (
    DeferWPFieldsManagerMixin,
    WPAwareManager,
    WPImportedPageMixin,
)
from venues.models import VenuePage


@register_singular_query_field("eventIndex")
class EventIndex(HeadlessMixin, Page):
    max_count = 1
    subpage_types = ["events.EventPage"]

    def future_events(self, info, **kwargs):
        return (
            EventPage.objects.live()
            .future()
            .order_by("next_occurrence")
            .select_related("featured_image")
            .prefetch_related(
                Prefetch(
                    "occurrences",
                    queryset=EventOccurrence.objects.select_related("venue"),
                ),
                "categories",
                Prefetch(
                    "organizers",
                    queryset=EventOrganizer.objects.select_related("association"),
                ),
            )
        )

    graphql_fields = [
        GraphQLCollection(
            GraphQLForeignKey,
            "future_events",
            "events.EventPage",
            required=True,
            item_required=True,
            is_queryset=True,
        ),
    ]

    search_fields = []


@register_snippet
@register_query_field("eventCategory", "eventCategories")
class EventCategory(models.Model):
    name = models.CharField(
        max_length=100,
        null=False,
        blank=False,
    )
    slug = models.SlugField(
        verbose_name=_("slug"),
        max_length=255,
        help_text=_("The name of the category as it will appear in URLs."),
    )
    show_in_filters = models.BooleanField(
        default=False,
        help_text=_("Should this category be available as a filter in the event programme?"),
    )

    PIG_CHOICES = [
        ("", _("None")),
    ] + ALL_PIGS

    pig = models.CharField(
        max_length=32,
        choices=PIG_CHOICES,
        default="",
        blank=True,
        help_text=_("Default pig for events of this kind."),
    )

    panels = [
        TitleFieldPanel("name"),
        FieldPanel("slug"),
        FieldPanel("show_in_filters"),
        FieldPanel("pig", heading=_("Pig")),
    ]

    graphql_fields = [
        GraphQLString("name", required=True),
        GraphQLString("slug", required=True),
        GraphQLBoolean("show_in_filters", required=True),
        GraphQLString("pig", required=True),
    ]

    class Meta:
        verbose_name = _("event category")
        verbose_name_plural = _("event categories")
        ordering = ["name"]

    def __str__(self):
        return self.name


class EventOrganizerLink(Orderable):
    event = ParentalKey(
        "events.EventPage", on_delete=models.CASCADE, related_name="organizer_links"
    )
    organizer = models.ForeignKey(
        "events.EventOrganizer",
        on_delete=models.PROTECT,
        related_name="organized_events",
        help_text="",
    )

    panels = [FieldPanel("organizer", heading="", help_text="")]

    graphql_fields = [
        GraphQLForeignKey("organizer", "events.EventOrganizer"),
    ]

    def __str__(self):
        return f"{self.organizer.name}"

    class Meta:
        verbose_name = _("organizer")
        verbose_name_plural = _("organizers")
        constraints = [
            UniqueConstraint(
                "event", "organizer", name="event_organizer_link_event_organizer_unique"
            )
        ]


# registered as a snippet via EventOrganizerSnippetViewSet in wagtail_hooks.py
@register_query_field("eventOrganizer", "eventOrganizers")
class EventOrganizer(index.Indexed, ClusterableModel):
    objects = WPAwareManager()

    name = models.CharField(
        max_length=100,
        null=False,
        blank=False,
    )
    slug = models.SlugField(
        verbose_name=_("slug"),
        max_length=255,
        help_text=_("The name of the organizer as it will appear in URLs."),
    )

    association = models.ForeignKey(
        "associations.AssociationPage",
        null=True,
        blank=True,
        on_delete=models.PROTECT,
        related_name="organizers",
        help_text=_("If a DNS association or committee is behind it, choose it here."),
    )

    external_url = models.URLField(
        blank=True,
        max_length=512,
        help_text=_("Link to the external organizer's website"),
    )

    panels = [
        TitleFieldPanel("name"),
        FieldPanel("slug"),
        FieldPanel(
            "association",
            widget=AssociationChooserWidget(linked_fields={"association": "#id_association"}),
            heading=_("Internal organizer"),
        ),
        MultiFieldPanel(
            heading=_("External organizer"),
            children=[
                FieldPanel(
                    "external_url",
                    heading=_("Website"),
                    help_text=_("Leave this empty if the organizer exists in the list above."),
                ),
            ],
        ),
    ]

    graphql_fields = [
        GraphQLString("name", required=True),
        GraphQLString("slug", required=True),
        GraphQLForeignKey("association", "associations.AssociationPage", required=False),
        GraphQLString("external_url"),
    ]

    search_fields = [
        index.SearchField("name"),
        index.AutocompleteField("name"),
    ]

    class Meta:
        verbose_name = _("event organizer")
        verbose_name_plural = _("event organizers")
        ordering = ["name"]

    def __str__(self):
        return self.name


class EventPageQuerySet(PageQuerySet):
    def future(self):
        now = timezone.now()
        today_start = timezone.localtime(now).replace(hour=0, minute=0, second=0, microsecond=0)
        next_occurrence = Min("occurrences__start", filter=Q(occurrences__start__gte=today_start))
        return self.filter(occurrences__start__gte=today_start).annotate(
            next_occurrence=next_occurrence
        )


class EventPageManager(
    DeferWPFieldsManagerMixin,
    PageManager.from_queryset(EventPageQuerySet),
):
    pass


class EventPage(HeadlessMixin, WPImportedPageMixin, Page):
    subpage_types = []
    parent_page_types = ["events.EventIndex"]
    show_in_menus = False

    objects = EventPageManager()

    featured_image = models.ForeignKey(
        "images.CustomImage",
        null=True,
        blank=True,
        on_delete=models.SET_NULL,
        related_name="+",
        help_text=_(
            "Choose an image for use in the programme and other surfaces. "
            "Should be a photo or an illustration without too much text "
            "– don't reuse a Facebook cover uncritically!"
        ),
    )

    subtitle = models.CharField(
        blank=True,
        max_length=128,
        help_text=_(
            "A short text that appears right below the title. "
            "Feel free to leave it empty if you fit most of it in the main title."
        ),
    )
    lead = RichTextField(features=["italic", "link"], blank=True)
    body = CommonStreamField

    categories = ParentalManyToManyField(
        "events.EventCategory",
        blank=True,
    )

    organizers = models.ManyToManyField(
        "events.EventOrganizer", through="events.EventOrganizerLink"
    )

    PIG_CHOICES = [
        ("", _("None")),
        ("automatic", _("Automatic")),
    ] + ALL_PIGS

    pig = models.CharField(
        max_length=32,
        choices=PIG_CHOICES,
        default="automatic",
        blank=True,
        help_text=_(
            "The pig that hangs out on the event page. "
            "Automatic causes one to be chosen based on the event's category."
        ),
    )

    ticket_url = models.URLField(
        blank=True,
        max_length=1024,
        help_text=_("Direct link to ticket purchase, e.g. TicketCo, Billetto or Ticketmaster"),
    )
    facebook_url = models.URLField(
        blank=True,
        max_length=1024,
        help_text=_("Direct link to the event on Facebook"),
    )

    free = models.BooleanField(null=False, default=False)
    price_regular = models.CharField(max_length=32, blank=True)
    price_student = models.CharField(max_length=32, blank=True)
    price_member = models.CharField(max_length=32, blank=True)

    ticket_panels = [
        FieldPanel("free", heading=_("Free"), help_text=_("Is this event free for everyone?")),
        MultiFieldPanel(
            children=[
                FieldRowPanel(
                    children=[
                        FieldPanel("price_regular", heading=_("Regular price")),
                        FieldPanel("price_student", heading=_("Price for students")),
                        FieldPanel("price_member", heading=_("Price for DNS members")),
                    ],
                    help_text="",
                ),
                HelpPanel(
                    content=mark_safe(
                        _(
                            "Write <strong>0</strong> for free. "
                            "An empty field hides the price category. "
                            "If possible, write digits only."
                        )
                    )
                ),
            ],
            attrs={"id": "specific_pricing_panel"},
        ),
        FieldPanel("ticket_url", heading=_("Ticket purchase link")),
    ]

    content_panels = Page.content_panels + [
        FieldPanel("subtitle", heading=_("Subtitle")),
        FieldPanel("featured_image"),
        FieldPanel("lead", heading=_("Lead")),
        FieldPanel("body"),
        FieldPanel("categories", widget=forms.CheckboxSelectMultiple),
        MultiFieldPanel(
            heading=_("Organizers"),
            children=[
                HelpPanel(
                    content=_("Who is behind the event?"),
                ),
                MultipleChooserPanel(
                    "organizer_links", chooser_field_name="organizer", label=_("Organizer")
                ),
            ],
        ),
        FieldPanel("pig", heading=_("Pig")),
        FieldPanel(
            "facebook_url",
            heading=_("Facebook link"),
            help_text=_("Direct link to the event on Facebook."),
        ),
        MultiFieldPanel(heading=_("Pricing and tickets"), children=ticket_panels),
        MultiFieldPanel(
            heading=_("Date, time and venue"),
            children=[
                HelpPanel(
                    content=_(
                        "If the event spans several days, add each day as a separate occurrence."
                    ),
                ),
                InlinePanel("occurrences", min_num=1, label=_("Occurrence")),
            ],
        ),
    ]

    graphql_fields = [
        GraphQLString("subtitle"),
        GraphQLImage("featured_image"),
        GraphQLRichText("lead"),
        GraphQLStreamfield("body"),
        GraphQLString("pig", required=True),
        GraphQLString("ticket_url"),
        GraphQLString("facebook_url"),
        GraphQLBoolean("free"),
        GraphQLString("price_regular"),
        GraphQLString("price_student"),
        GraphQLString("price_member"),
        GraphQLCollection(
            GraphQLForeignKey,
            "categories",
            "events.EventCategory",
            required=True,
            item_required=True,
        ),
        GraphQLCollection(
            GraphQLForeignKey,
            "occurrences",
            "events.EventOccurrence",
            required=True,
            item_required=True,
        ),
        GraphQLCollection(
            GraphQLForeignKey,
            "organizers",
            "events.EventOrganizer",
            required=True,
            item_required=True,
        ),
    ]

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

    class Meta:
        verbose_name = _("event")
        verbose_name_plural = _("events")

    def clean(self):
        super().clean()

        # remove duped organizers based on name
        organizers = [x.organizer.name for x in self.organizer_links.all()]
        if len(organizers) != len(set(organizers)):
            seen_organizers = set()
            for link in self.organizer_links.all():
                if link.organizer.name in seen_organizers:
                    self.organizer_links.remove(link)
                else:
                    seen_organizers.add(link.organizer.name)

        # if the event is free, all specific pricing is unset
        if self.free:
            self.price_regular = ""
            self.price_student = ""
            self.price_member = ""


class EventOccurrence(Orderable):
    objects = WPAwareManager()

    event = ParentalKey(EventPage, on_delete=models.CASCADE, related_name="occurrences")
    start = models.DateTimeField()
    end = models.DateTimeField(null=True, blank=True)
    venue = models.ForeignKey(
        VenuePage,
        on_delete=models.PROTECT,
        related_name="event_occurrences",
        blank=True,
        null=True,
    )
    venue_custom = models.CharField(
        blank=True,
        max_length=128,
        help_text=mark_safe(
            _(
                "Use this <em>if none of the venues that can be selected on the left</em> fit. "
                "E.g. <em>Frederikkeplassen</em> or <em>Sirkusteltet</em>."
            )
        ),
    )

    panels = [
        FieldRowPanel(
            children=[
                FieldPanel("start", heading=_("Start")),
                FieldPanel("end", heading=_("End")),
            ],
        ),
        FieldRowPanel(
            children=[
                FieldPanel("venue", heading=_("Venue"), widget=forms.Select),
                FieldPanel("venue_custom", heading=_("Venue as free text")),
            ],
        ),
    ]

    graphql_fields = [
        GraphQLString("start", required=True),
        GraphQLString("end"),
        GraphQLForeignKey("venue", "venues.VenuePage"),
        GraphQLString("venue_custom"),
    ]

    def clean(self):
        if self.venue_custom:
            trimmed = self.venue_custom.strip()
            self.venue_custom = trimmed
            if trimmed:
                match = VenuePage.objects.filter(title=trimmed).first()
                if match:
                    self.venue = match
                    self.venue_custom = ""

        if self.venue and self.venue_custom:
            raise ValidationError(
                {
                    "venue_custom": _(
                        "You can't both pick a venue and write something in this field."
                    )
                }
            )
        if not self.venue and not self.venue_custom:
            raise ValidationError({"venue": _("Venue is required.")})

    def __str__(self):
        return f"{self.start}--{self.end}"

    class Meta:
        verbose_name = _("occurrence")
        verbose_name_plural = _("occurrences")
