from datetime import datetime

from django.contrib.contenttypes.models import ContentType
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 django_iban.fields import IBANField
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


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)

    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()

            for registration in event.registrations.all():
                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):
    # 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)

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

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

    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=8, 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",
    )

    accept_sepa = models.BooleanField(verbose_name=_("SEPA direct debit"))
    iban = IBANField(
        verbose_name=_("IBAN (for SEPA direct debit)"),
        enforce_database_constraint=True,
        null=True,
        blank=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)

        invoice, __ = Invoice.objects.get_or_create(for_content_type=ContentType.objects.get_for_model(self), for_object_id=self.pk, defaults={
            "group": group,
            "variant": "sepa" if self.accept_sepa else "transfer",
            "transaction_id": 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_address_city": self.person.place,
            "billing_address_postcode": self.person.postal_code,
            "billing_email": self.perosn.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=self.event.cost, currency="EUR", sku="", tax_rate=7)

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

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

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

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

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