import datetime
from simple_history.models import HistoricalRecords
from django.db import models
from apps.core.models import BaseModel
from apps.authorization.models import UserRelations
from apps.authentication.models import Organization
from django.contrib.postgres.fields import ArrayField
from datetime import datetime
import jdatetime


class LivestockGroup(models.TextChoices):
    ROOSTAEI = "rural", "روستایی"  # noqa
    SANATI = "industrial", "صنعتی"  # noqa
    ASHAYERI = "nomadic", "عشایری"  # noqa


class LivestockType(models.TextChoices):
    LIGHT = "light", "سبک"
    HEAVY = "heavy", "سنگین"  # noqa


class LivestockSubtype(models.TextChoices):
    MILKING = "milking", "شیری"  # noqa
    FATTENING = "fattening", "پرواری"  # noqa


# Create your models here.

class ProductCategory(BaseModel):
    """ Category for products """

    name = models.CharField(max_length=250, default='empty')  # noqa
    type_choices = (
        ('free', 'Free'),  # free product
        ('gov', 'Governmental')  # government product
    )
    type = models.CharField(max_length=5, choices=type_choices, default='empty')
    img = models.CharField(max_length=100, default='empty')
    parent = models.ForeignKey(
        'self',
        on_delete=models.CASCADE,
        related_name='parents',
        null=True
    )

    def __str__(self):
        return f'name: {self.name} - type: {self.type}'

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


class Product(BaseModel):
    """ Child of reference product - like: brown rice """

    name = models.CharField(max_length=250, default='empty')  # noqa
    product_id = models.BigIntegerField(default=0)
    type_choices = (
        ('free', 'FREE'),  # free product
        ('gov', 'GOVERNMENTAL')  # government product
    )
    type = models.CharField(max_length=5, choices=type_choices)
    img = models.CharField(max_length=100, default='empty')
    category = models.ForeignKey(
        ProductCategory,
        on_delete=models.CASCADE,
        related_name='products',
        null=True
    )

    def generate_product_id(self):  # noqa
        """ generate id for product from 10 """

        last = Product.objects.filter(product_id__gte=10, product_id__lte=1999).order_by('-product_id').first()
        if last:
            next_code = last.product_id + 1
        else:
            next_code = 10
        return next_code

    def quota_information(self):
        """
        quotas information of product
        """

        # number of quotas
        quotas_count = self.quotas.filter(is_closed=False).count()

        # total weight of product that assigned in quota
        total_quotas_weight = self.quotas.filter(is_closed=False).aggregate(
            total=models.Sum('quota_weight')
        )['total'] or 0

        # total remaining weight of product quotas
        total_remaining_quotas_weight = self.quotas.filter(is_closed=False).aggregate(
            total=models.Sum('remaining_weight')
        )['total'] or 0

        total_distributed_weight = QuotaDistribution.objects.filter(
            quota__product_id=self.id,
            quota__is_closed=False
        ).aggregate(total_weight=models.Sum('weight'))['total_weight'] or 0

        total_sold = QuotaDistribution.objects.filter(
            quota__product_id=self.id,
            quota__is_closed=False
        ).aggregate(total_sold=models.Sum('been_sold'))['total_sold'] or 0

        total_warehouse_entry = QuotaDistribution.objects.filter(
            quota__product_id=self.id,
            quota__is_closed=False
        ).aggregate(total_entry=models.Sum('warehouse_entry'))['total_entry'] or 0

        data = {
            'product': self.id,
            'quotas_count': quotas_count,
            'total_quotas_weight': total_quotas_weight,
            'total_remaining_quotas_weight': total_remaining_quotas_weight,
            'total_distributed_weight': total_distributed_weight,
            'total_sold': total_sold,
            'total_warehouse_entry': total_warehouse_entry,
        }

        return data

    def __str__(self):
        return f'name: {self.name} - type: {self.type}'

    def save(self, *args, **kwargs):
        if not self.product_id:
            self.product_id = self.generate_product_id()  # set product id
        super(Product, self).save(*args, **kwargs)


class Attribute(BaseModel):
    """
    every reference product have multiple attributes
    """
    product = models.ForeignKey(
        Product,
        on_delete=models.CASCADE,
        related_name='attributes',
        null=True
    )
    name = models.CharField(max_length=100, default='empty')
    type = models.ForeignKey(
        'SaleUnit',
        on_delete=models.CASCADE,
        related_name="attributes",
        null=True
    )

    is_global = models.BooleanField(default=False)

    def __str__(self):
        return f'{self.product.name} - {self.name}'

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


class AttributeValue(BaseModel):
    """
    every child product should have attribute value for
    reference product attribute
    """

    quota = models.ForeignKey(
        'Quota',
        on_delete=models.CASCADE,
        related_name='attribute_values',
        null=True
    )
    attribute = models.ForeignKey(
        Attribute,
        on_delete=models.CASCADE,
        related_name='values',
        null=True
    )
    value = models.IntegerField(default=0)

    def __str__(self):
        return f'Quota({self.quota.id}) - {self.attribute.name} - {self.value}'

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


class Broker(BaseModel):
    """ Broker for product """

    BROKER_TYPES = (
        ('public', 'PUBLIC'),
        ('exclusive', 'EXCLUSIVE')
    )
    name = models.CharField(max_length=255, null=True)
    product = models.ForeignKey(
        Product,
        on_delete=models.CASCADE,
        related_name='product_broker',
        null=True
    )
    organization = models.ForeignKey(
        Organization,
        on_delete=models.CASCADE,
        related_name='product_organization',
        null=True
    )
    calculation_strategy = models.ForeignKey(
        'SaleUnit',
        on_delete=models.CASCADE,
        related_name='brokers',
        null=True
    )
    broker_type = models.CharField(choices=BROKER_TYPES, max_length=20, null=True)
    required = models.BooleanField(default=False)

    def __str__(self):
        return f'{self.organization.name} - {self.product.name}'

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


class SaleUnit(BaseModel):
    """ Units of product for sale """

    product = models.ForeignKey(
        Product,
        on_delete=models.CASCADE,
        related_name='sale_unit',
        null=True
    )
    unit = models.CharField(max_length=250, null=True, unique=True)
    required = models.BooleanField(default=False)

    def __str__(self):
        return f'{self.product.name} - {self.unit}'

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


class IncentivePlan(BaseModel):
    """ incentive plan for every quota """

    PLAN_TYPE_CHOICES = (
        ('ILQ', 'increasing livestock quotas'),
        ('SM', 'statistical/monitoring')
    )
    GROUP_CHOICES = (
        ('industrial', 'Industrial'),
        ('rural', 'Rural'),
        ('nomadic', 'Nomadic')
    )
    name = models.CharField(max_length=255)
    description = models.TextField(blank=True, null=True)
    registering_organization = models.ForeignKey(
        UserRelations,
        on_delete=models.CASCADE,
        related_name='incentive_plans',
        null=True
    )
    plan_type = models.CharField(choices=PLAN_TYPE_CHOICES, max_length=5, null=True)
    group = models.CharField(choices=GROUP_CHOICES, max_length=15, null=True)
    is_time_unlimited = models.BooleanField(default=False)
    start_date_limit = models.DateField(null=True, blank=True)
    end_date_limit = models.DateField(null=True, blank=True)

    def __str__(self):
        return self.name

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


class Quota(BaseModel):
    """ quota for product with some conditions """

    registerer_organization = models.ForeignKey(
        Organization,
        on_delete=models.CASCADE,
        related_name='quotas',
        null=True
    )
    assigned_organizations = models.ManyToManyField(
        Organization,
        related_name='assigned_quotas',
        blank=True
    )
    quota_id = models.PositiveBigIntegerField(null=True, blank=True)
    quota_code = models.CharField(max_length=15, null=True)
    quota_weight = models.PositiveIntegerField(default=0)
    remaining_weight = models.PositiveBigIntegerField(default=0)
    quota_distributed = models.PositiveIntegerField(default=0)
    product = models.ForeignKey(
        Product,
        on_delete=models.CASCADE,
        related_name='quotas',
        null=True
    )
    sale_type = models.CharField(max_length=50, choices=[("free", "آزاد"), ("gov", "دولتی")])  # noqa
    sale_unit = models.ForeignKey(
        SaleUnit,
        on_delete=models.CASCADE,
        related_name='quotas',
        null=True
    )
    month_choices = ArrayField(base_field=models.IntegerField(), null=True)
    sale_license = ArrayField(base_field=models.IntegerField(), null=True)
    group = models.CharField(
        max_length=50,
        choices=[("rural", "روستایی"), ("industrial", "صنعتی"), ("nomadic", "عشایری")]  # noqa
    )
    has_distribution_limit = models.BooleanField(default=False)
    distribution_mode = ArrayField(base_field=models.IntegerField(), blank=True, null=True)
    base_price_factory = models.DecimalField(max_digits=12, decimal_places=2)
    base_price_cooperative = models.DecimalField(max_digits=12, decimal_places=2)
    final_price = models.DecimalField(max_digits=12, decimal_places=2, null=True, blank=True)
    is_closed = models.BooleanField(default=False)
    closed_at = models.DateTimeField(null=True, blank=True)

    def __str__(self):
        return f"Quota ({self.id}) for {self.product.name}"

    def generate_quota_id(self):  # noqa
        """ generate id for quota from 1001 """

        last = Quota.objects.filter(quota_id__gte=1001, quota_id__lte=1999).order_by('-quota_id').first()
        if last:
            next_code = last.quota_id + 1
        else:
            next_code = 1001
        return next_code

    def calculate_final_price(self):
        """ calculate final price of quota """

        factor_total = sum([
            f.value for f in self.attribute_values.all()
        ])
        broker_total = sum([
            b.value for b in self.broker_values.all()
        ])
        coop = self.base_price_cooperative or 0
        factory = self.base_price_factory or 0

        return factor_total + broker_total + coop + factory

    @property
    def remaining_quota_weight(self):
        """ calculate remaining quota weight after distribution """
        distributed_weight = self.distributions_assigned.aggregate(total=models.Sum("weight"))["total"] or 0
        return self.quota_weight - distributed_weight

    def is_in_valid_time(self):
        """ check if quota allowed time for distribute, sale, etc is expired"""
        now = datetime.now()
        persian_date = jdatetime.datetime.fromgregorian(datetime=now)

        return persian_date.month in self.month_choices

    def save(self, calculate_final_price=None, *args, **kwargs):
        if not self.quota_id:
            self.quota_id = self.generate_quota_id()
        if calculate_final_price:
            if not self.final_price:
                self.final_price = self.calculate_final_price()
        return super(Quota, self).save(*args, **kwargs)


class QuotaIncentiveAssignment(BaseModel):
    """ assign incentive plan to quota """

    quota = models.ForeignKey(
        Quota,
        on_delete=models.CASCADE,
        related_name="incentive_assignments",
        null=True
    )
    incentive_plan = models.ForeignKey(
        IncentivePlan,
        on_delete=models.CASCADE,
        related_name='quota_assignment',
        null=True
    )
    heavy_value = models.DecimalField(max_digits=12, decimal_places=2)
    light_value = models.DecimalField(max_digits=12, decimal_places=2)

    def __str__(self):
        return f"Quota ({self.quota.id}) for {self.incentive_plan.name}"

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


class QuotaBrokerValue(BaseModel):
    """ broker attributes value for quota """

    quota = models.ForeignKey(
        Quota,
        on_delete=models.CASCADE,
        related_name="broker_values",
        null=True
    )
    broker = models.ForeignKey(
        Broker,
        on_delete=models.CASCADE,
        related_name='values'
    )
    value = models.DecimalField(max_digits=12, decimal_places=2)

    def __str__(self):
        return f"Quota ({self.quota.id}) for Broker({self.broker.organization.name})"

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


class QuotaLivestockAllocation(BaseModel):
    """ livestock allocation to quota """

    quota = models.ForeignKey(
        "Quota",
        on_delete=models.CASCADE,
        related_name="livestock_allocations",
        null=True
    )
    livestock_group = models.CharField(max_length=20, choices=LivestockGroup.choices, null=True)
    livestock_type = models.CharField(max_length=20, choices=LivestockType.choices, null=True)
    livestock_subtype = models.CharField(max_length=20, choices=LivestockSubtype.choices, null=True)
    quantity_kg = models.DecimalField(max_digits=12, decimal_places=2, null=True)

    """ 
    @using for set unique values between fields
    class Meta:
        unique_together = ('quota', 'livestock_group', 'livestock_type', 'livestock_subtype')
    """

    def __str__(self):
        return f"{self.livestock_group} - {self.livestock_type}/{self.livestock_subtype}: {self.quantity_kg}kg"

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


class QuotaLiveStockAgeLimitation(BaseModel):
    quota = models.ForeignKey(
        Quota,
        on_delete=models.CASCADE,
        related_name='livestock_age_limitations',
        null=True
    )
    livestock_type = models.CharField(max_length=20, choices=LivestockType.choices, null=True)
    livestock_subtype = models.CharField(max_length=20, choices=LivestockSubtype.choices, null=True)
    age_month = models.PositiveIntegerField(default=0)

    def __str__(self):
        return f"{self.livestock_type}/{self.livestock_subtype}: {self.age_month} month"

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


class QuotaDistribution(BaseModel):
    assigner_organization = models.ForeignKey(
        Organization,
        on_delete=models.CASCADE,
        related_name='distributions_assigner',
        null=True
    )
    assigned_organization = models.ForeignKey(
        Organization,
        on_delete=models.CASCADE,
        related_name='distributions',
        null=True
    )
    description = models.TextField(max_length=1000, null=True)
    distribution_id = models.CharField(max_length=20, null=True)
    quota = models.ForeignKey(
        Quota,
        on_delete=models.CASCADE,
        related_name='distributions_assigned',
        null=True
    )
    weight = models.PositiveBigIntegerField(default=0)
    warehouse_entry = models.PositiveBigIntegerField(default=0)
    warehouse_balance = models.PositiveBigIntegerField(default=0)
    been_sold = models.PositiveBigIntegerField(default=0)
    history = HistoricalRecords()

    def generate_distribution_id(self):
        """ generate special id for quota distribution """

        year = jdatetime.datetime.now().year
        month = jdatetime.datetime.now().month
        day = jdatetime.datetime.now().day
        product_id = self.quota.product.product_id
        quota_id = self.quota.quota_id

        base_code = f"{str(year)[3]}{month}{day}{product_id}{quota_id}"

        similar_codes = QuotaDistribution.objects.filter(distribution_id__startswith=base_code).count()
        counter = str(similar_codes + 1).zfill(4)

        return f"{base_code}{counter}"

    def __str__(self):
        return f"{self.distribution_id}-"

    def save(self, *args, **kwargs):
        if not self.distribution_id:
            self.distribution_id = self.generate_distribution_id()
        return super(QuotaDistribution, self).save(*args, **kwargs)
