from django.db import models
from django.db.models import Q
from django.db.transaction import atomic
from rest_framework import serializers, status

from apps.herd.models import Rancher
from apps.herd.pos.api.v1.serializers import RancherSerializer
from apps.herd.services.services import get_rancher_statistics, rancher_quota_weight
from apps.pos_device.models import POSFreeProducts
from apps.pos_device.pos.api.v1.serializers.device import DeviceSerializer
from apps.pos_device.services.services import pos_organizations_sharing_information
from apps.product.exceptions import QuotaSaleTimeException
from apps.product.models import (
    QuotaDistribution,
    Product, OrganizationQuotaStats
)
from apps.product.services.services import (
    quota_live_stock_allocation_info,
    quota_incentive_plans_info,
    quota_attribute_value
)
from apps.warehouse import models as warehouse_models
from apps.warehouse.exceptions import WareHouseException
from apps.warehouse.services.quota_usage_services import QuotaUsageService
from apps.warehouse.services.services import create_extra_sale, create_pre_sale


class InventoryEntrySerializer(serializers.ModelSerializer):
    class Meta:
        model = warehouse_models.InventoryEntry
        fields = [
            "id",
            "entry_identity",
            "create_date",
            "modify_date",
            "organization",
            "distribution",
            "weight",
            "balance",
            "lading_number",
            "delivery_address",
            "is_confirmed",
            "notes",
        ]

    def to_representation(self, instance):
        """ custom output of inventory entry serializer """

        representation = super().to_representation(instance)
        if instance.document:
            representation['document'] = instance.document
        if instance.distribution:
            # distribution data
            representation['distribution'] = {
                'distribution_identity': instance.distribution.distribution_id,
                'sale_unit': instance.distribution.quota.sale_unit.unit,
                'id': instance.distribution.id
            }

            representation['quota'] = {
                'quota_identity': instance.distribution.quota.quota_id,
                'quota_weight': instance.distribution.quota.quota_weight,
                'quota_livestock_allocations': quota_live_stock_allocation_info(
                    instance.distribution.quota
                ),
                'quota_incentive_plans': quota_incentive_plans_info(instance.distribution.quota)
            }

            representation['product'] = {
                'image': instance.distribution.quota.product.img,
                'name': instance.distribution.quota.product.name,
                'id': instance.distribution.quota.product.id,
            }

            representation['pricing'] = {
                'pricing_attributes': quota_attribute_value(instance.distribution.quota),
                'sharing': pos_organizations_sharing_information(
                    self.context['device'],
                    instance.distribution.quota,
                    instance.distribution
                ),
                'base_prices': [
                    {
                        "text": "درب کارخانه",  # noqa
                        "name": "base_price_factory",
                        "value": instance.distribution.quota.base_price_factory
                    },
                    {
                        "text": "درب اتحادیه",  # noqa
                        "name": "base_price_cooperative",
                        "value": instance.distribution.quota.base_price_cooperative
                    }
                ]
            }

        if 'rancher' in self.context.keys():
            # rancher herd & live stock statistics
            representation['rancher_statistics'] = get_rancher_statistics(self.context['rancher'])

            # rancher live stock statistics by inventory entry
            representation['rancher_quota_weight_statistics'] = rancher_quota_weight(
                self.context['rancher'], instance
            )

        return representation


class InventoryQuotaSaleTransactionSerializer(serializers.ModelSerializer):
    rancher_national_code = serializers.CharField(max_length=50, required=False)

    class Meta:  # noqa
        model = warehouse_models.InventoryQuotaSaleTransaction
        fields = '__all__'
        depth = 0

    def create(self, validated_data):
        items_data = self.context['request'].data['items']
        rancher_code = validated_data.pop(
            'rancher_national_code'
        ) if 'rancher_national_code' in self.context['request'].data.keys() else None

        with atomic():
            # get rancher with national code
            rancher = None  # noqa
            if rancher_code:
                rancher = Rancher.objects.get(national_code=rancher_code)
                validated_data['rancher'] = rancher

            # if transaction exists, update transaction status
            transaction = self.Meta.model.objects.filter(
                transaction_id=validated_data.get('transaction_id')
            )
            if transaction.exists():
                transaction = transaction.first()

                # --- Case 1: success => only update items
                if transaction.transaction_status == 'success':
                    for item_data in items_data:
                        qs = warehouse_models.InventoryQuotaSaleItem.objects.filter(
                            Q(free_product_id=item_data.get('free_product', None)),
                            Q(gov_product_id=item_data.get('gov_product', None)),
                            transaction=transaction,
                        ).update(**item_data)
                    return transaction

                # --- Case 2: not success => update transaction fields + items
                for field in [
                    'transaction_status',
                    'transaction_status_code',
                    'result_text',
                    'ref_num',
                    'terminal',
                    'payer_cart',
                    'pos_date',
                    'transaction_date',
                ]:
                    if field in validated_data:
                        setattr(transaction, field, validated_data[field])

                transaction.save(update_fields=[
                    'transaction_status',
                    'transaction_status_code',
                    'result_text',
                    'ref_num',
                    'terminal',
                    'payer_cart',
                    'pos_date',
                    'transaction_date',
                ])

                # items can change
                for item_data in items_data:
                    items = warehouse_models.InventoryQuotaSaleItem.objects.filter(
                        Q(free_product_id=item_data.get('free_product', None)),
                        Q(gov_product_id=item_data.get('gov_product', None)),
                        transaction=transaction,
                    )
                    items.update(**item_data)

                    # if transaction status updated as success, call signal for inventory management
                    if validated_data['transaction_status'] == 'success':
                        for sale_item in items:
                            sale_item.inventory_calculation = True
                            sale_item.save()

                return transaction

            # --- Case 3: create new transaction
            transaction = warehouse_models.InventoryQuotaSaleTransaction.objects.create(
                seller_organization=self.context['organization'],
                pos_device=self.context['pos_device'],
                **validated_data
            )

            # calculate total price of product items in shopping cart
            total_price = 0
            for item_data in items_data:
                # get product by type
                gov_product = item_data.pop('gov_product', None)
                free_product = item_data.pop('free_product', None)

                distribution_id = item_data.pop(
                    'quota_distribution'
                ) if 'quota_distribution' in item_data.keys() else None

                distribution = QuotaDistribution.objects.filter(
                    id=distribution_id
                ).first() if distribution_id else None

                quota_stat_id = item_data.pop('quota_stat') if 'quota_stat' in item_data.keys() else None
                quota_stat = OrganizationQuotaStats.objects.get(
                    id=quota_stat_id
                ) if quota_stat_id else None

                # create item for transaction
                item = warehouse_models.InventoryQuotaSaleItem.objects.create(
                    transaction=transaction,
                    **{'quota_distribution': distribution} if distribution else {},
                    **{'quota_stat': quota_stat} if quota_stat else {},
                    gov_product=Product.objects.get(
                        id=gov_product
                    ) if Product.objects.filter(id=gov_product).exists() else None,
                    free_product=POSFreeProducts.objects.get(
                        id=free_product
                    ) if POSFreeProducts.objects.filter(id=free_product).exists() else None,
                    **item_data
                )
                total_price += item.total_price

                # IF WE DO NOT HAVE DISTRIBUTION, THEN IT IS A FREE PRODUCT TRANSACTION
                if distribution_id and quota_stat_id in item_data.keys():
                    # create extra sale for distribution
                    create_extra_sale(transaction=transaction, sale_item=item)

                    # create pre sale for distribution
                    create_pre_sale(transaction=transaction, sale_item=item)

                    # calculate quota usage of rancher
                    QuotaUsageService.allocate_usage(
                        rancher=rancher,
                        distribution=distribution,
                        quota_stat=quota_stat,
                        item_data=item_data
                    )

            transaction.transaction_price = total_price
            transaction.save()

            return transaction

    def validate(self, attrs):
        """
        validate total inventory sale should be fewer than
        distribution weight
        """

        items = self.context['request'].data['items']
        for item in items:
            if 'quota_stat' in item.keys():

                # get quota stat object
                quota_stat = OrganizationQuotaStats.objects.get(
                    id=item.get('quota_stat')
                )
                if not quota_stat.quota.pre_sale and not quota_stat.quota.free_sale:
                    # if quota has not been in sale time
                    if not quota_stat.quota.is_in_sale_licence_time():
                        raise QuotaSaleTimeException()
                    total_sale_weight = quota_stat.sale_items.aggregate(
                        total=models.Sum('weight')
                    )['total'] or 0

                    if total_sale_weight + item.get('weight') > quota_stat.remaining_amount:
                        raise WareHouseException(
                            "وزن وارد شده بیشتر از باقیمانده سهمیه میباشد",  # noqa
                            status_code=status.HTTP_403_FORBIDDEN
                        )

        return attrs

    def to_representation(self, instance):
        """ customize output of transactions serializer """

        representation = super().to_representation(instance)

        if instance.rancher:
            representation['rancher'] = RancherSerializer(instance.rancher).data
        representation['pos_device'] = DeviceSerializer(instance.pos_device).data
        if instance.seller_organization:
            representation['seller_organization'] = instance.seller_organization.name

        # get product items of transaction
        representation['items'] = InventoryQuotaSaleItemSerializer(instance.items.all(), many=True).data

        return representation


class InventoryQuotaSaleItemSerializer(serializers.ModelSerializer):
    product_name = serializers.CharField(source='product.name', read_only=True)

    class Meta:
        model = warehouse_models.InventoryQuotaSaleItem
        fields = [
            'id',
            "transaction",
            "quota_stat",
            "gov_product",
            "free_product",
            "product_name",
            "image",
            "name",
            "price_type",
            "delivery_type",
            "item_type",
            "paid_type",
            "weight",
            "unit_price",
            "total_price",
            "paid_price",
            "livestock_statistic",
            "item_share",
        ]


class QuotaPreSaleItemSerializer(serializers.ModelSerializer):
    class Meta:
        model = warehouse_models.QuotaPreSaleItem
        fields = '__all__'
