from datetime import datetime
from decimal import Decimal

from django.contrib.contenttypes.models import ContentType
from django.core.exceptions import ValidationError
from django.db import models
from django.urls import reverse
from django.utils.text import slugify
from django.utils.timezone import now
from django.utils.translation import gettext_lazy as _

from ckeditor.fields import RichTextField
from colorfield.fields import ColorField
from payments import PurchasedItem

from aleksis.apps.tezor.models.base import Client
from aleksis.apps.tezor.models.invoice import Invoice, InvoiceGroup
from aleksis.core.mixins import ExtensibleModel
from aleksis.core.models import Group, Person
from aleksis.core.util.core_helpers import generate_random_code, get_site_preferences
from aleksis.core.util.email import send_email

from .data_checks import EventMembersSyncDataCheck


class RegistrationState(ExtensibleModel):

    name = models.CharField(verbose_name=_("Name"), max_length=255)
    colour = ColorField(blank=True, verbose_name=_("Colour"))

    def __str__(self) -> str:
        return self.name


class Terms(ExtensibleModel):
    title = models.CharField(max_length=255, verbose_name=_("Title"))
    term = RichTextField(verbose_name=_("Term"))
    confirmation_text = models.TextField(verbose_name=_("Confirmation text"))

    def __str__(self) -> str:
        return self.title


class InfoMailing(ExtensibleModel):
    subject = models.CharField(max_length=255, verbose_name=_("subject"))
    text = RichTextField(verbose_name=_("Text"))
    reply_to = models.EmailField(verbose_name=_("Request replies to"), blank=True)

    active = models.BooleanField(verbose_name=_("Mailing is active"), default=False)

    sender = models.EmailField(verbose_name=_("Sender"), blank=True)
    send_to_person = models.BooleanField(verbose_name=_("Send to registered person"), default=True)
    send_to_guardians = models.BooleanField(verbose_name=_("Send to guardians"), default=False)
    send_to_retracted = models.BooleanField(
        verbose_name=_("Send to participants who retracted"), default=False
    )
    send_to_not_checked_in = models.BooleanField(
        verbose_name=_("Send to participants who did not check in"), default=True
    )

    def __str__(self) -> str:
        return self.subject

    @classmethod
    def get_active_mailings(cls):
        return cls.objects.filter(active=True)

    def send(self):
        for event in self.events.all():
            through = EventInfoMailingThrough.objects.get(info_mailing=self, event=event)
            sent_to = through.sent_to.all()

            filter_args = {}
            if not self.send_to_retracted:
                filter_args["retracted"] = False
            if not self.send_to_not_checked_in:
                filter_args["checked_in"] = True

            for registration in event.registrations.filter(**filter_args):
                if registration.person in sent_to:
                    continue

                subject = self.subject.format(
                    event=event, registration=registration, person=registration.person
                )
                body = self.text.format(
                    event=event, registration=registration, person=registration.person
                )

                if self.send_to_person:
                    to = [registration.person.email]
                    if self.send_to_guardians:
                        cc = registration.person.guardians.values_list("email", flat=True).all()
                    else:
                        cc = []
                elif self.send_to_guardians:
                    to = registration.person.guardians.values_list("email", flat=True).all()
                    cc = []

                sender = self.sender or get_site_preferences()["mail__address"]
                reply_to = self.reply_to or sender

                context = {"subject": subject, "body": body}
                send_email(
                    template_name="info_mailing",
                    context=context,
                    from_email=sender,
                    recipient_list=to,
                    cc=cc,
                    headers={
                        "Reply-To": reply_to,
                    },
                )

                through.sent_to.add(registration.person)


class Event(ExtensibleModel):

    data_checks = [EventMembersSyncDataCheck]

    # Event details
    display_name = models.CharField(verbose_name=_("Display name"), max_length=255)
    linked_group = models.OneToOneField(
        Group, on_delete=models.CASCADE, verbose_name=_("Group"), related_name="linked_event"
    )
    description = models.CharField(max_length=500, verbose_name=_("Description"))
    published = models.BooleanField(default=False, verbose_name=_("Publish"))
    place = models.CharField(max_length=50, verbose_name="Place")
    slug = models.SlugField(max_length=255, verbose_name=_("Slug"), blank=True)

    # Date details
    date_event = models.DateField(verbose_name=_("Date of event"))
    date_registration = models.DateField(verbose_name=_("Registration deadline"))
    date_retraction = models.DateField(verbose_name=_("Retraction deadline"))

    # Other details
    cost = models.IntegerField(verbose_name=_("Cost in €"))
    max_participants = models.PositiveSmallIntegerField(verbose_name=_("Maximum participants"))
    information = RichTextField(verbose_name=_("Information about the event"))
    terms = models.ManyToManyField(Terms, verbose_name=_("Terms"), related_name="event", blank=True)
    info_mailings = models.ManyToManyField(
        InfoMailing,
        verbose_name=_("Info mailings"),
        related_name="events",
        through="EventInfoMailingThrough",
        blank=True,
    )

    def save(self, *args, **kwargs):
        if not self.slug:
            if self.linked_group.short_name:
                self.slug = slugify(self.linked_group.short_name)
            else:
                self.slug = slugify(self.display_name)

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

    def __str__(self) -> str:
        return self.display_name

    def sync_group_members(self):
        self.linked_group.members.set(
            self.registrations.filter(retracted=False).values_list("person", flat=True)
        )

    def can_register(self, request=None):
        now = datetime.today().date()

        if request and request.user.is_authenticated:
            if request.user.person in self.linked_group.members.all():
                return False

            if EventRegistration.objects.filter(person=request.user.person).exists():
                return False

            if (
                Voucher.objects.filter(event=self, person=request.user.person, used=False).count()
                > 0
            ):
                return True

        if self.linked_group.members.count() >= self.max_participants:
            return False

        if self.date_registration:
            return self.date_registration >= now
        return self.date_event > now

    def get_absolute_url(self):
        return reverse("event_by_name", kwargs={"slug": self.slug})

    @property
    def booked_percentage(self):
        return self.linked_group.members.count() / self.max_participants * 100

    @property
    def members_persons(self):
        return self.linked_group.members.all()

    @property
    def owners_persons(self):
        return self.linked_group.owners.all()

    @classmethod
    def upcoming_published_events(cls):
        return Event.objects.filter(published=True, date_event__gte=now())


class EventInfoMailingThrough(ExtensibleModel):
    event = models.ForeignKey(Event, on_delete=models.CASCADE)
    info_mailing = models.ForeignKey(InfoMailing, on_delete=models.CASCADE)

    sent_to = models.ManyToManyField(
        Person,
        verbose_name=_("Sent to persons"),
        related_name="received_info_mailings",
        editable=False,
        blank=True,
    )


class Voucher(ExtensibleModel):
    class Meta:
        verbose_name = _("Vouchers")
        verbose_name_plural = _("Vouchers")

    code = models.CharField(max_length=255, blank=True, default="")
    event = models.ForeignKey(
        Event,
        related_name="vouchers",
        verbose_name=_("Event"),
        on_delete=models.CASCADE,
        null=True,
    )
    person = models.ForeignKey(
        Person,
        related_name="vouchers",
        verbose_name=_("Person"),
        on_delete=models.CASCADE,
    )
    discount = models.IntegerField(default=100)

    used = models.BooleanField(default=False)
    used_person_uid = models.ForeignKey(
        Person,
        on_delete=models.CASCADE,
        verbose_name=_("Used by"),
        related_name="used_vouchers",
        null=True,
    )
    deleted = models.BooleanField(default=False)

    def __str__(self) -> str:
        return self.code

    def save(self, *args, **kwargs):
        if not self.code:
            self.code = generate_random_code(5, 3)
        super().save(*args, **kwargs)


class EventRegistration(ExtensibleModel):

    event = models.ForeignKey(
        Event, on_delete=models.CASCADE, verbose_name=_("Event"), related_name="registrations"
    )
    person = models.ForeignKey(Person, on_delete=models.CASCADE, verbose_name=_("Person"))
    date_registered = models.DateTimeField(auto_now_add=True, verbose_name=_("Registration date"))

    school = models.CharField(verbose_name=_("Name of school"), max_length=255)
    school_class = models.CharField(verbose_name=_("School class"), max_length=255)
    school_place = models.CharField(verbose_name=_("Place of the school"), max_length=255)

    comment = models.TextField(verbose_name=_("Comment / remarks"), blank=True, default="")
    medical_information = models.TextField(
        verbose_name=_("Medical information / intolerances"), blank=True, default=""
    )
    voucher = models.ForeignKey(
        Voucher,
        on_delete=models.CASCADE,
        verbose_name=_("Voucher"),
        blank=True,
        null=True,
    )
    donation = models.PositiveIntegerField(verbose_name=_("Donation"), blank=True, null=True)
    accepted_terms = models.ManyToManyField(
        Terms,
        verbose_name=_("Accepted terms"),
        related_name="registrations",
    )

    states = models.ManyToManyField(
        RegistrationState, verbose_name=_("States"), related_name="registrations"
    )

    retracted = models.BooleanField(verbose_name=_("Retracted"), default=False)
    retracted_date = models.DateField(verbose_name=_("Retracted at"), null=True, blank=True)

    checked_in = models.BooleanField(verbose_name=_("Checked in"), default=False)
    checked_in_date = models.DateTimeField(verbose_name=_("Checked in at"), null=True, blank=True)

    def mark_checked_in(self):
        if not self.checked_in:
            self.checked_in = True
            self.checked_in_date = now()
            self.save()
        else:
            raise ValidationError(_("Person is already checked in!"))

    def retract(self):
        # Remove person from group
        self.event.linked_group.members.remove(self.person)
        # Mark registration as retracted
        self.retracted = True
        self.retracted_date = datetime.today()
        self.save()

    def get_person(self):
        return self.person

    def get_billing_email_recipients(self):
        return [self.person.email] + list(self.person.guardians.values_list("email", flat=True))

    def get_invoice(self):
        # FIXME Maybe do not hard-code this
        client, __ = Client.objects.get_or_create(name="Teckids e.V.")
        group, __ = InvoiceGroup.objects.get_or_create(
            name="Hack'n'Fun-Veranstaltungen",
            client=client,
            defaults={
                "template_name": "paweljong/invoice_pdf.html",
            },
        )

        invoice, __ = Invoice.objects.get_or_create(
            for_content_type=ContentType.objects.get_for_model(self),
            for_object_id=self.pk,
            defaults={
                "group": group,
                "number": f"HNF-{self.date_registered.strftime('%Y-%m')}-{self.id}",
                "currency": "EUR",
                "total": self._get_total_amount()[0],
                "tax": self._get_total_amount()[1],
                "description": _("Participation of {} in event {}").format(
                    self.person.addressing_name, self.event.display_name
                ),
                "billing_first_name": self.person.first_name,
                "billing_last_name": self.person.last_name,
                "billing_address_1": f"{self.person.street} {self.person.housenumber}",
                "billing_city": self.person.place,
                "billing_postcode": self.person.postal_code,
                "billing_email": self.person.email,
            },
        )

        return invoice

    def get_purchased_items(self):
        # FIXME Maybe do not hard-code the tax rate and currency
        # First, return main amount
        yield PurchasedItem(
            name=self.event.display_name,
            quantity=1,
            price=Decimal(self.event.cost / 1.07),
            currency="EUR",
            sku="EVENT",
            tax_rate=7,
        )

        # If a dnoation was made, add it
        if self.donation:
            yield PurchasedItem(
                name=_("Social Sponsoring / Extra Donation"),
                quantity=1,
                price=Decimal(self.donation),
                currency="EUR",
                sku="DONAT",
                tax_rate=0,
            )

        # If a voucher was used, add it
        if self.voucher:
            yield PurchasedItem(
                name=_("Voucher / Granted discount"),
                quantity=1,
                price=Decimal(-1 * self.voucher.discount * (self.event.cost / 1.07) / 100),
                currency="EUR",
                sku="DISCO",
                tax_rate=7,
            )

    def _get_total_amount(self):
        total, total_tax = 0, 0
        for item in self.get_purchased_items():
            tax = item.price * item.tax_rate / 100
            total += item.price + tax
            total_tax += tax
        return total, total_tax

    def __str__(self) -> str:
        return f"{self.event}, {self.person.first_name} {self.person.last_name}"

    def save(self, *args, **kwargs):
        self.event.sync_group_members()
        super().save(*args, **kwargs)

    def delete(self, *args, **kwargs):
        self.event.sync_group_members()
        super().delete(*args, **kwargs)

    class Meta:
        verbose_name = _("Event registration")
        verbose_name_plural = _("Event registrations")
        constraints = [
            models.UniqueConstraint(
                fields=["person", "event"], name="unique_person_registration_per_event"
            )
        ]


class Checkpoint(ExtensibleModel):
    event = models.ForeignKey(
        Event, verbose_name=_("Related event"), related_name="checkpoints", on_delete=models.CASCADE
    )
    person = models.ForeignKey(
        Person,
        verbose_name=_("Checked person"),
        related_name="event_checkpoints",
        on_delete=models.CASCADE,
    )
    checked_by = models.ForeignKey(
        Person,
        verbose_name=_("Checked by person"),
        related_name="event_checkpoints_created",
        on_delete=models.CASCADE,
    )

    comment = models.CharField(max_length=60, verbose_name=_("Comment"))

    timestamp = models.DateTimeField(verbose_name=_("Date and time of check"), auto_now_add=True)
    lat = models.DecimalField(
        max_digits=10, decimal_places=8, verbose_name=_("Latitude of check"), blank=True, null=True
    )
    lon = models.DecimalField(
        max_digits=11, decimal_places=8, verbose_name=_("Longitude of check"), blank=True, null=True
    )