import random

import base36
from django.core.exceptions import ValidationError
from django.db import models
from django.urls import reverse
from django.utils import timezone

from la_chariotte.settings import AUTH_USER_MODEL


class GroupedOrder(models.Model):
    name = models.CharField(
        max_length=100, null=True, verbose_name="Titre de la commande"
    )
    orga = models.ForeignKey(
        AUTH_USER_MODEL, on_delete=models.CASCADE, verbose_name="Organisateur·ice"
    )
    delivery_date = models.DateField("Date de livraison")
    delivery_slot = models.CharField(
        max_length=50, null=True, blank=True, verbose_name="Créneau de distribution"
    )
    deadline = models.DateTimeField("Date limite de commande")
    place = models.CharField(
        max_length=100, null=True, blank=True, verbose_name="Lieu de livraison"
    )
    description = models.TextField("Description", null=True, blank=True)
    code = models.CharField(auto_created=True)
    is_phone_mandatory = models.BooleanField(
        default=False, verbose_name="Numéro de téléphone obligatoire"
    )

    def create_code_from_pk(self):
        """When a grouped order is created, a unique code is generated, to be used to
        build the order URL.

        Using a simple code (rather than a UUID) makes it easy to be manually written.

        This code is generated using :
        1. The record private key, written in base36 (max 5 digits)
        2. A random int written in base36 (5 digits long)

        Only the 6 first digits of this string are used.

        The use of the private key guarantees the uniqueness, and the random part makes
        the URL path hard to guess.
        """
        code_length = 6
        base_36_pk = base36.dumps(self.pk)
        # The random int is between 11111[in base 36] and zzzzz[in base 36],
        # which are the smallest and the biggest values with 5 digits.
        random_string = base36.dumps(
            random.randint(pow(36, code_length - 2), pow(36, code_length - 1) - 1)
        )
        self.code = f"{base_36_pk}{random_string}"[:code_length]

    @property
    def total_price(self):
        price = 0
        for order in self.order_set.all():
            price += order.price
        return price

    def get_total_ordered_items(self):
        total_nb = 0
        for item in self.item_set.all():
            total_nb += item.ordered_nb
        return total_nb

    def is_open(self):
        return self.deadline >= timezone.now()

    def is_to_be_delivered(self):
        return self.delivery_date >= timezone.now().date()

    def get_absolute_url(self):
        return reverse("order:manage_items", kwargs={"code": self.code})

    def clean_fields(self, exclude=None):
        super().clean_fields(exclude=exclude)
        # Ensure that new grouped orders use a delivery_date in the future.
        # (updating an existing one with a date in the past is still possible)
        if self.delivery_date < timezone.now().date() and not self.pk:
            raise ValidationError("La date de livraison ne doit pas être dans le passé")

        if self.delivery_date < self.deadline.date():
            raise ValidationError(
                "La date limite de commande doit être avant la date de livraison"
            )

    def __str__(self):  # pragma: no cover
        return (
            self.name
            if self.name
            else (
                f"Commande groupée {self.code} du {self.date} organisée par {self.orga}"
            )
        )


class OrderAuthor(models.Model):
    """Used when a user orders (with or without an account)"""

    first_name = models.CharField(verbose_name="Prénom")
    last_name = models.CharField(verbose_name="Nom")
    phone = models.CharField(
        verbose_name="Numéro de téléphone",
        help_text="Pour que l'organisateur·ice vous contacte en cas de besoin",
    )
    email = models.CharField(
        verbose_name="Adresse mail",
        help_text="Pour que l'organisateur·ice vous contacte en cas de besoin",
    )

    def __str__(self):  # pragma: no cover
        return f"{self.first_name} {self.last_name}"


class Order(models.Model):
    grouped_order = models.ForeignKey(
        GroupedOrder, on_delete=models.CASCADE, related_name="order_set"
    )
    author = models.ForeignKey(OrderAuthor, on_delete=models.CASCADE)
    created_date = models.DateTimeField("Date et heure de commande", auto_now_add=True)
    note = models.TextField(max_length=200, null=True, blank=True)

    @property
    def articles_nb(self):
        """Computes the number of articles in this order"""
        articles_nb = 0
        for ord_item in self.ordered_items.all():
            articles_nb += ord_item.nb
        return articles_nb

    @property
    def price(self):
        """Computes the total price of the order"""
        price = 0
        for ord_item in self.ordered_items.all():
            price += ord_item.get_price()
        return price

    def __str__(self):  # pragma: no cover
        return (
            f"Commande de {self.author} pour la commande groupée"
            f" {self.grouped_order.code}"
        )


class Item(models.Model):
    name = models.CharField(max_length=100)
    grouped_order = models.ForeignKey(GroupedOrder, on_delete=models.CASCADE)
    price = models.DecimalField(max_digits=10, decimal_places=2)
    max_limit = models.PositiveSmallIntegerField(null=True, blank=True)

    @property
    def ordered_nb(self):
        """Computes the number of times this item has been ordered"""
        ordered_nb = 0
        for order in self.orders.all():
            ordered_nb += order.nb
        return ordered_nb

    def get_total_price(self):
        """Returns the total price of all orders on this item"""
        return self.price * self.ordered_nb

    def get_remaining_nb(self):
        """Returns the number of remaining articles for this item"""
        if self.max_limit is not None:
            return self.max_limit - self.ordered_nb
        else:
            return None

    def get_absolute_url(self):
        return reverse("order:manage_items", kwargs={"code": self.grouped_order.code})

    def __str__(self):  # pragma: no cover
        return f"{self.name} ({self.price} €)"

    class Meta:
        ordering = ["name"]


class OrderedItem(models.Model):
    """Item in one specific Order, and the number of articles"""

    nb = models.PositiveSmallIntegerField(default=0)
    order = models.ForeignKey(
        Order, on_delete=models.CASCADE, related_name="ordered_items"
    )
    item = models.ForeignKey(Item, on_delete=models.CASCADE, related_name="orders")

    def get_price(self):
        return self.nb * self.item.price

    def __str__(self):  # pragma: no cover
        return f"{self.nb} {self.item}, dans la commande {self.order.pk}"
