import time
from decimal import Decimal
from datetime import datetime
import random
import string
import uuid
from io import BytesIO

from django.conf import settings
from django.contrib.admin import display
from django.contrib.auth.hashers import make_password
from django.contrib.auth.models import AbstractUser, UserManager as _UserManager
from django.core.files.base import ContentFile
from django.core.validators import MinValueValidator, MaxValueValidator
from django.db import models
from django.db.models import F, Case, When, DecimalField, Prefetch
from django.db.models.lookups import GreaterThan
from django.utils import timezone
from django.utils.translation import gettext_lazy as _
from django_cleanup import cleanup

from store.utils import create_preview, concat_not_null_values


class GlobalSettings(models.Model):
    cached = None

    # currency
    yuan_rate = models.DecimalField('Курс CNY/RUB', max_digits=10, decimal_places=2, default=0)
    # Chinadelivery
    delivery_price_CN = models.DecimalField('Цена доставки по Китаю', max_digits=10, decimal_places=2, default=0)
    commission_rub = models.DecimalField('Комиссия, руб', max_digits=10, decimal_places=2, default=0)
    pickup_address = models.CharField('Адрес пункта самовывоза', max_length=200, blank=True, null=True)

    class Meta:
        verbose_name = 'Глобальные настройки'
        verbose_name_plural = 'Глобальные настройки'

    def save(self, *args, **kwargs):
        # Store only one instance of GlobalSettings
        self.__class__.objects.exclude(id=self.id).delete()
        super().save(*args, **kwargs)
        GlobalSettings.cached = self

    def __str__(self) -> str:
        return f'GlobalSettings for {self.id}'

    @classmethod
    def load(cls) -> 'GlobalSettings':
        if cls.cached is not None:
            return cls.cached

        obj, _ = cls.objects.get_or_create(id=1)
        cls.cached = obj
        return obj


class Category(models.Model):
    name = models.CharField('Название', max_length=20)
    slug = models.SlugField('Идентификатор', unique=True)
    delivery_price_CN_RU = models.DecimalField('Цена доставки Китай-РФ', max_digits=10, decimal_places=2, default=0)  # Chinadelivery2

    def __str__(self):
        return self.name

    class Meta:
        verbose_name = 'Категория'
        verbose_name_plural = 'Категории'


class UserQuerySet(models.QuerySet):
    pass


class UserManager(models.Manager.from_queryset(UserQuerySet), _UserManager):
    def _create_user(self, email, password, **extra_fields):
        if not email:
            raise ValueError("The given email must be set")
        email = self.normalize_email(email)
        user = self.model(email=email, **extra_fields)
        user.password = make_password(password)
        user.save(using=self._db)
        return user

    def create_user(self, email=None, password=None, **extra_fields):
        extra_fields.setdefault("is_staff", False)
        return self._create_user(email, password, **extra_fields)

    def create_superuser(self, email=None, password=None, **extra_fields):
        extra_fields.setdefault("is_staff", True)
        extra_fields.setdefault("job_title", User.ADMIN)

        if extra_fields.get("is_staff") is not True:
            raise ValueError("Superuser must have is_staff=True.")

        return self._create_user(email, password, **extra_fields)


class User(AbstractUser):
    ADMIN = "admin"
    ORDER_MANAGER = "ordermanager"
    PRODUCT_MANAGER = "productmanager"
    
    JOB_CHOICES = (
        (ADMIN, 'Администратор'),
        (ORDER_MANAGER, 'Менеджер по заказам'),
        (PRODUCT_MANAGER, 'Менеджер по закупкам'),
    )

    # Login by email
    USERNAME_FIELD = 'email'
    REQUIRED_FIELDS = []
    username = None
    email = models.EmailField("Эл. почта", unique=True)

    first_name = models.CharField(_("first name"), max_length=150, blank=True, null=True)
    last_name = models.CharField(_("last name"), max_length=150, blank=True, null=True)
    middle_name = models.CharField("Отчество", max_length=150, blank=True, null=True)
    job_title = models.CharField("Должность", max_length=30, choices=JOB_CHOICES)

    objects = UserManager()

    @property
    def is_superuser(self):
        return self.job_title == self.ADMIN

    @display(description='ФИО')
    def full_name(self):
        return concat_not_null_values(self.last_name, self.first_name, self.middle_name)


class PromocodeQuerySet(models.QuerySet):
    def active(self):
        return self.filter(is_active=True)


class Promocode(models.Model):
    name = models.CharField('Название', max_length=100, unique=True)
    discount = models.DecimalField('Скидка', max_digits=10, decimal_places=2,
                                   validators=[MinValueValidator(0), MaxValueValidator(100)])
    free_delivery = models.BooleanField('Бесплатная доставка', default=False)  # freedelivery
    no_comission = models.BooleanField('Без комиссии', default=False)  # nocomission
    is_active = models.BooleanField('Активен', default=True)

    objects = PromocodeQuerySet.as_manager()

    def __str__(self):
        return self.name

    class Meta:
        verbose_name = 'Промокод'
        verbose_name_plural = 'Промокоды'


class PaymentMethod(models.Model):
    name = models.CharField('Название', max_length=30)
    slug = models.SlugField('Идентификатор', unique=True)
    cardnumber = models.CharField('Номер карты', max_length=30)
    requisites = models.CharField('Реквизиты', max_length=200)

    class Meta:
        verbose_name = 'Метод оплаты'
        verbose_name_plural = 'Методы оплаты'

    def __str__(self):
        return self.name


@cleanup.select
class Image(models.Model):
    image = models.ImageField(upload_to='checklist_images')
    is_preview = models.BooleanField(default=False)
    checklist = models.ForeignKey('Checklist', on_delete=models.CASCADE, related_name='images')

    class Meta:
        verbose_name = 'Изображение'
        verbose_name_plural = 'Изображения'

    def __str__(self):
        return getattr(self.image, 'name')


def generate_checklist_id():
    """ Generate unique id for Checklist """

    all_ids = Checklist.objects.all().values_list('id', flat=True)
    allowed_chars = string.ascii_letters + string.digits

    while True:
        generated_id = ''.join(random.choice(allowed_chars) for _ in range(settings.CHECKLIST_ID_LENGTH))
        if generated_id not in all_ids:
            return generated_id


class ChecklistQuerySet(models.QuerySet):
    def with_base_related(self):
        return self.select_related('manager', 'category', 'payment_method', 'promocode')\
            .prefetch_related(Prefetch('images', to_attr='_images'))

    def default_ordering(self):
        return self.order_by(F('status_updated_at').desc(nulls_last=True))

    def annotate_price_rub(self):
        yuan_rate = GlobalSettings.load().yuan_rate
        return self.annotate(_price_rub=F('price_yuan') * yuan_rate)

    def annotate_commission_rub(self):
        commission = GlobalSettings.load().commission_rub
        return self.annotate(_commission_rub=Case(
            When(GreaterThan(F("_price_rub"), 150_000), then=F("_price_rub") * settings.COMMISSION_OVER_150K),
            default=commission,
            output_field=DecimalField()
        ))


@cleanup.select
class Checklist(models.Model):
    # Statuses
    class Status:
        DRAFT = "draft"
        NEW = "neworder"
        PAYMENT = "payment"
        BUYING = "buying"
        BOUGHT = "bought"
        CHINA = "china"
        CHINA_RUSSIA = "chinarush"
        RUSSIA = "rush"
        SPLIT_WAITING = "split_waiting"
        SPLIT_PAID = "split_paid"
        CDEK = "cdek"
        COMPLETED = "completed"

        PDF_AVAILABLE_STATUSES = (RUSSIA, SPLIT_WAITING, SPLIT_PAID, CDEK, COMPLETED)

        CHOICES = (
            (DRAFT, 'Черновик'),
            (NEW, 'Новый заказ'),
            (PAYMENT, 'Проверка оплаты'),
            (BUYING, 'На закупке'),
            (BOUGHT, 'Закуплен'),
            (CHINA, 'На складе в Китае'),
            (CHINA_RUSSIA, 'Доставка на склад РФ'),
            (RUSSIA, 'На складе в РФ'),
            (SPLIT_WAITING, 'Сплит: ожидание оплаты 2й части'),
            (SPLIT_PAID, 'Сплит: полностью оплачено'),
            (CDEK, 'Доставляется СДЭК'),
            (COMPLETED, 'Завершен'),
        )

    # Delivery
    class DeliveryType:
        PICKUP = "pickup"
        CDEK = "cdek"

        CHOICES = (
            (PICKUP, 'Самовывоз из шоурума'),
            (CDEK, 'Пункт выдачи заказов CDEK'),
        )

    created_at = models.DateTimeField(auto_now_add=True)
    status_updated_at = models.DateTimeField('Дата обновления статуса заказа', auto_now_add=True)
    id = models.CharField(primary_key=True, max_length=settings.CHECKLIST_ID_LENGTH,
                          default=generate_checklist_id, editable=False)
    status = models.CharField('Статус заказа', max_length=15, choices=Status.CHOICES, default=Status.NEW)
    # managerid
    manager = models.ForeignKey('User', verbose_name='Менеджер', on_delete=models.SET_NULL, blank=True, null=True)
    product_link = models.URLField('Ссылка на товар', null=True, blank=True)
    category = models.ForeignKey('Category', verbose_name="Категория", blank=True, null=True, on_delete=models.SET_NULL)
    subcategory = models.CharField('Подкатегория', max_length=20, blank=True, null=True)

    brand = models.CharField('Бренд', max_length=100, null=True, blank=True)
    model = models.CharField('Модель', max_length=100, null=True, blank=True)
    size = models.CharField('Размер', max_length=30, null=True, blank=True)

    # curencycurency2
    price_yuan = models.DecimalField('Цена в юанях', max_digits=10, decimal_places=2, default=0)
    # TODO: replace real_price by parser
    real_price = models.DecimalField('Реальная цена', max_digits=10, decimal_places=2, null=True, blank=True)

    # promo
    promocode = models.ForeignKey('Promocode', verbose_name='Промокод', on_delete=models.PROTECT, null=True, blank=True)
    comment = models.CharField('Комментарий', max_length=200, null=True, blank=True)

    # buyername
    buyer_name = models.CharField('Имя покупателя', max_length=100, null=True, blank=True)
    # buyerphone
    buyer_phone = models.CharField('Телефон покупателя', max_length=100, null=True, blank=True)
    # tg
    buyer_telegram = models.CharField('Telegram покупателя', max_length=100, null=True, blank=True)

    # receivername
    receiver_name = models.CharField('Имя получателя', max_length=100, null=True, blank=True)
    # reveiverphone
    receiver_phone = models.CharField('Телефон получателя', max_length=100, null=True, blank=True)

    is_split_payment = models.BooleanField('Оплата частями', default=False)

    # paymenttype
    payment_method = models.ForeignKey('PaymentMethod', verbose_name='Метод оплаты',
                                       null=True, blank=True,
                                       on_delete=models.SET_NULL)
    payment_proof = models.ImageField('Подтверждение оплаты', upload_to='docs', null=True, blank=True)  # paymentproovement
    receipt = models.ImageField('Фото чека', upload_to='docs', null=True, blank=True)  # checkphoto

    delivery = models.CharField('Тип доставки', max_length=10, choices=DeliveryType.CHOICES, null=True, blank=True)
    # trackid
    poizon_tracking = models.CharField('Трек-номер Poizon', max_length=100, null=True, blank=True)
    cdek_tracking = models.CharField('Трек-номер СДЭК', max_length=100, null=True, blank=True)
    cdek_barcode_pdf = models.FileField('Штрих-код СДЭК в PDF', upload_to='docs', null=True, blank=True)

    objects = ChecklistQuerySet.as_manager()

    class Meta:
        verbose_name = 'Заказ'
        verbose_name_plural = 'Заказы'

    @property
    def price_rub(self) -> Decimal:
        # Prefer annotated field
        if hasattr(self, '_price_rub'):
            return self._price_rub

        return GlobalSettings.load().yuan_rate * self.price_yuan

    @property
    def full_price(self) -> Decimal:
        price = self.price_rub

        free_delivery = False
        no_comission = False

        if self.promocode_id:
            promocode = self.promocode
            price -= promocode.discount * self.price_rub

            free_delivery = promocode.free_delivery
            no_comission = promocode.no_comission

        if not free_delivery:
            price += GlobalSettings.load().delivery_price_CN + self.delivery_price_CN_RU

        if not no_comission:
            price += self.commission_rub

        return price

    @property
    def delivery_price_CN_RU(self) -> Decimal:
        return getattr(self.category, 'delivery_price_CN_RU', 0.0)

    @property
    def commission_rub(self) -> Decimal:
        # Prefer annotated field
        if hasattr(self, '_commission_rub'):
            return self._commission_rub

        return (self.price_rub * Decimal(settings.COMMISSION_OVER_150K)
                if self.price_rub > 150_000
                else GlobalSettings.load().commission_rub)

    @property
    def preview_image(self):
        # Prefer annotated field
        if hasattr(self, '_images'):
            return next(filter(lambda x: x.is_preview, self._images), None)

        return self.images.filter(is_preview=True).first()

    @property
    def preview_image_url(self):
        return getattr(self.preview_image, 'image', None)

    @property
    def main_images(self):
        # Prefer prefetched field
        if hasattr(self, '_images'):
            return [img for img in self._images if not img.is_preview]

        return self.images.filter(is_preview=False)

    @property
    def title(self):
        return concat_not_null_values(self.brand, self.model)

    def __str__(self):
        return f'{self.id}'

    def save(self, *args, **kwargs):
        if self.id:
            old_obj = Checklist.objects.filter(id=self.id).first()
            # If status was updated, update status_updated_at field
            if old_obj and self.status != old_obj.status:
                self.status_updated_at = timezone.now()

            # Try to get CDEK barcode PDF
            if not self.cdek_barcode_pdf and self.cdek_tracking and self.status in Checklist.Status.PDF_AVAILABLE_STATUSES:
                from store.views import CDEKAPI
                pdf_file = CDEKAPI.client.get_barcode_file(self.cdek_tracking)
                if pdf_file:
                    self.cdek_barcode_pdf.save(f'{self.id}_barcode.pdf', pdf_file)

            # Create preview image
            if self.images.exists() and not self.preview_image:
                # Render preview image
                original = self.images.first().image
                preview = create_preview(original.path, size=self.size, price_rub=self.price_rub, title=self.title)

                # Prepare bytes
                image_io = BytesIO()
                preview.save(image_io, format='JPEG')

                # Create Image model and save it
                image_obj = Image(is_preview=True, checklist_id=self.id)
                image_obj.image.save(name=f'{self.id}_preview.jpg',
                                     content=ContentFile(image_io.getvalue()),
                                     save=True)

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

