import logging
from decimal import Decimal
from PIL import Image
from measurement.measures import Weight

from django.db import models
from django.db.models.functions import Coalesce
from django.conf import settings
from django.utils import timezone
from django.urls import reverse
from django.core.validators import MinValueValidator, MaxValueValidator
from django.core.serializers.json import DjangoJSONEncoder
from django.forms.models import model_to_dict

from django_measurement.models import MeasurementField

from accounts.models import User, Address

from . import (
    DiscountValueType,
    VoucherType,
    TransactionStatus,
    OrderStatus,
    ShippingMethodType
)
from .weight import WeightUnits, zero_weight

logger = logging.getLogger(__name__)


class ProductEncoder(DjangoJSONEncoder):
    def default(self, obj):
        logger.info(f"\n{obj}\n")
        return super().default(obj)


class ProductManager(models.Manager):
    def get_queryset(self):
        return super().get_queryset().annotate(
            num_ordered=models.Sum('order_lines__quantity')
        )


class Product(models.Model):
    name = models.CharField(max_length=250)
    subtitle = models.CharField(max_length=250, blank=True)
    description = models.TextField(blank=True)
    sku = models.CharField(max_length=255, unique=True)
    stripe_id = models.CharField(max_length=255, blank=True)
    price = models.DecimalField(
        max_digits=settings.DEFAULT_MAX_DIGITS,
        decimal_places=settings.DEFAULT_DECIMAL_PLACES,
        blank=True,
        null=True,
    )
    weight = MeasurementField(
        measurement=Weight,
        unit_choices=WeightUnits.CHOICES,
        blank=True,
        null=True
    )

    visible_in_listings = models.BooleanField(default=False)
    sorting = models.PositiveIntegerField(blank=True, null=True)

    created_at = models.DateTimeField(auto_now_add=True)
    updated_at = models.DateTimeField(auto_now=True)

    objects = ProductManager()

    def __str__(self):
        return self.name

    def get_absolute_url(self):
        return reverse('dashboard:product-detail', kwargs={'pk': self.pk})

    def get_first_img(self):
        return self.productphoto_set.first()

    def get_second_img(self):
        try:
            return self.productphoto_set.all()[1]
        except IndexError:
            return 'No image'

    class Meta:
        ordering = ['sorting', 'name']


class ProductPhoto(models.Model):
    product = models.ForeignKey(Product, on_delete=models.CASCADE)
    image = models.ImageField(upload_to='products/images')

    def __str__(self):
        return self.product.name

    def delete(self, *args, **kwargs):
        storage, path = self.image.storage, self.image.path

        super(ProductPhoto, self).delete(*args, **kwargs)
        storage.delete(path)

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

    #     img = Image.open(self.image.path)
        # if img.height > 400 or img.width > 400:
        #     output_size = (400, 400)
        #     img.thumbnail(output_size)
        #     img.save(self.image.path)


class Coupon(models.Model):
    type = models.CharField(
        max_length=20,
        choices=VoucherType.CHOICES,
        default=VoucherType.ENTIRE_ORDER
    )
    name = models.CharField(max_length=255, null=True, blank=True)
    code = models.CharField(max_length=12, unique=True, db_index=True)
    valid_from = models.DateTimeField(default=timezone.now)
    valid_to = models.DateTimeField(null=True, blank=True)

    discount_value_type = models.CharField(
        max_length=10,
        choices=DiscountValueType.CHOICES,
        default=DiscountValueType.FIXED,
    )
    discount_value = models.DecimalField(
        max_digits=settings.DEFAULT_MAX_DIGITS,
        decimal_places=settings.DEFAULT_DECIMAL_PLACES,
    )

    products = models.ManyToManyField(Product, blank=True)
    users = models.ManyToManyField(User, blank=True)

    class Meta:
        ordering = ("code",)

    def __str__(self):
        return self.name

    @property
    def is_valid(self):
        today = timezone.localtime(timezone.now())
        return True if today >= self.valid_from and today <= self.valid_to else False

    def get_absolute_url(self):
        return reverse('dashboard:coupon-detail', kwargs={'pk': self.pk})


class ShippingMethod(models.Model):
    name = models.CharField(max_length=100)
    type = models.CharField(max_length=30, choices=ShippingMethodType.CHOICES)
    price = models.DecimalField(
        max_digits=settings.DEFAULT_MAX_DIGITS,
        decimal_places=settings.DEFAULT_DECIMAL_PLACES,
        default=0,
    )

    def get_absolute_url(self):
        return reverse('dashboard:shipmeth-detail', kwargs={'pk': self.pk})


class OrderManager(models.Manager):
    def with_lines(self):
        return self.select_related('lines')

    def without_drafts(self):
        return self.exclude(
            status=OrderStatus.DRAFT
        )

    def with_fulfillment(self):
        return self.annotate(
            total_quantity_fulfilled=models.Sum('lines__quantity_fulfilled'),
            total_quantity_ordered=models.Sum('lines__quantity')
        )

    def with_fulfillment_and_filter(self, query=None):
        return self.annotate(
            total_quantity_fulfilled=models.Sum('lines__quantity_fulfilled'),
            total_quantity_ordered=models.Sum('lines__quantity')
        ).filter(pk=query)


class Order(models.Model):
    customer = models.ForeignKey(
        User,
        related_name="orders",
        on_delete=models.SET_NULL,
        null=True
    )
    status = models.CharField(
        max_length=32,
        default=OrderStatus.UNFULFILLED,
        choices=OrderStatus.CHOICES
    )
    billing_address = models.ForeignKey(
        Address,
        related_name="+",
        editable=False,
        null=True,
        on_delete=models.SET_NULL
    )
    shipping_address = models.ForeignKey(
        Address,
        related_name="+",
        editable=False,
        null=True,
        on_delete=models.SET_NULL
    )
    shipping_method = models.ForeignKey(
        ShippingMethod,
        blank=True,
        null=True,
        related_name="orders",
        on_delete=models.SET_NULL
    )

    coupon = models.ForeignKey(
        Coupon,
        related_name='orders',
        on_delete=models.SET_NULL,
        blank=True,
        null=True
    )

    shipping_total = models.DecimalField(
        max_digits=5,
        decimal_places=2,
        default=0
    )

    total_net_amount = models.DecimalField(
        max_digits=10,
        decimal_places=2,
        default=0
    )

    weight = MeasurementField(
        measurement=Weight,
        unit_choices=WeightUnits.CHOICES,
        default=zero_weight,
        blank=True,
        null=True
    )

    created_at = models.DateTimeField(auto_now_add=True, editable=False)
    updated_at = models.DateTimeField(auto_now=True)

    objects = OrderManager()

    def get_total_quantity(self):
        return sum([line.quantity for line in self])

    def get_discount(self):
        if self.coupon:
            if self.coupon.discount_value_type == DiscountValueType.FIXED:
                return self.coupon.discount_value
            elif self.coupon.discount_value_type == DiscountValueType.PERCENTAGE:
                return (self.coupon.discount_value / Decimal('100')) * self.total_net_amount
        return Decimal('0')

    def get_total_price_after_discount(self):
        return round((self.total_net_amount - self.get_discount()) + self.shipping_total, 2)

    def get_absolute_url(self):
        return reverse('dashboard:order-detail', kwargs={'pk': self.pk})

    class Meta:
        ordering = ('-created_at',)


class Transaction(models.Model):
    status = models.CharField(
        max_length=32,
        blank=True,
        default=TransactionStatus.CREATED,
        choices=TransactionStatus.CHOICES
    )
    paypal_id = models.CharField(max_length=64, blank=True)
    confirmation_email_sent = models.BooleanField(default=False)
    order = models.OneToOneField(
        Order,
        editable=False,
        blank=True,
        null=True,
        on_delete=models.CASCADE
    )


class OrderLine(models.Model):
    order = models.ForeignKey(
        Order,
        related_name="lines",
        editable=False,
        on_delete=models.CASCADE
    )
    product = models.ForeignKey(
        Product,
        related_name="order_lines",
        on_delete=models.SET_NULL,
        blank=True,
        null=True,
    )
    quantity = models.IntegerField(validators=[MinValueValidator(1)])
    quantity_fulfilled = models.IntegerField(
        validators=[MinValueValidator(0)], default=0
    )
    customer_note = models.TextField(blank=True, default="")

    currency = models.CharField(
        max_length=settings.DEFAULT_CURRENCY_CODE_LENGTH,
        default=settings.DEFAULT_CURRENCY,
    )

    unit_price = models.DecimalField(
        max_digits=settings.DEFAULT_MAX_DIGITS,
        decimal_places=settings.DEFAULT_DECIMAL_PLACES,
    )

    tax_rate = models.DecimalField(
        max_digits=5, decimal_places=2, default=Decimal("0.0")
    )

    def get_total(self):
        return self.unit_price * self.quantity

    @property
    def quantity_unfulfilled(self):
        return self.quantity - self.quantity_fulfilled


class TrackingNumber(models.Model):
    order = models.ForeignKey(
        Order,
        related_name="tracking_numbers",
        editable=False,
        on_delete=models.CASCADE
    )
    tracking_id = models.CharField(max_length=256)

    created_at = models.DateTimeField(auto_now_add=True, editable=False)
    updated_at = models.DateTimeField(auto_now=True)

    class Meta:
        verbose_name = 'Tracking Number'
        verbose_name_plural = 'Tracking Numbers'

    def __str__(self):
        return self.tracking_id


class Subscription(models.Model):
    stripe_id = models.CharField(max_length=255, blank=True)
    customer = models.OneToOneField(
        User,
        related_name='subscription',
        on_delete=models.SET_NULL,
        null=True
    )
