from django.db import models
from rest_framework import serializers
from rest_framework.exceptions import APIException

from apps.product import models as product_models
from apps.product.exceptions import (
    QuotaWeightException,
    QuotaClosedException,
    QuotaExpiredTimeException,
    QuotaLimitByOrganizationException
)
from apps.product.web.api.v1.serializers import product_serializers
from apps.product.web.api.v1.serializers.quota_serializers import QuotaBrokerValueSerializer, QuotaSerializer


class QuotaDistributionSerializer(serializers.ModelSerializer):
    class Meta:
        model = product_models.QuotaDistribution
        fields = '__all__'
        extra_kwargs = {
            'assigner_organization': {
                'required': False
            },
            'description': {
                'required': False
            }
        }

    def validate(self, data):
        """
        @ to validate if distribution weight
        more than quota weight raise exception
        @ if quota is closed raise exception
        @ if quota has organization limit, before distribution
        check assigned organization
        """

        quota = data['quota']
        assigned_organization = data['assigned_organization']
        amount = data['weight']
        instance_id = self.instance.id if self.instance else None
        parent_distribution = data['parent_distribution'] if 'parent_distribution' in data.keys() else None

        # check quota expired time
        if not quota.is_in_valid_time():
            raise QuotaExpiredTimeException()

        # check if quota is closed
        if quota.is_closed:
            raise QuotaClosedException()

        # check if assigned organization is in quota limitation
        if quota.has_organization_limit is True and assigned_organization not in quota.limit_by_organizations.all():
            raise QuotaLimitByOrganizationException()

        # total quota distributions weight
        total = product_models.QuotaDistribution.objects.filter(
            quota=quota
        ).exclude(id=instance_id).aggregate(
            total=models.Sum('weight')
        )['total'] or 0

        # if parent distribution is none compare with main quota weight
        # else compare with parent distribution weight
        if not self.instance:
            if not parent_distribution:
                if total + amount > quota.quota_weight:
                    raise QuotaWeightException()
            else:
                if amount > parent_distribution.remaining_weight:
                    raise QuotaWeightException()

        if self.instance:
            # total warehouse inventory entry
            total_entry = self.instance.inventory_entry.aggregate(
                total=models.Sum('weight')
            )['total'] or 0

            # if inventory entry weight is bigger than distribute weight in edit distribution
            if total_entry > self.instance.weight:
                raise APIException("وزن وارد شده کمتر از وزن ورودی به انبار است", code=403)  # noqa

            if self.instance.parent_distribution:
                if amount > self.instance.parent_distribution.remaining_weight + self.instance.parent_distribution.distributed:
                    raise QuotaWeightException()

            # if weight is more than distribution remaining weight
            if not self.instance.parent_distribution:
                if amount + total > self.instance.quota.quota_weight:
                    raise APIException("وزن وارد شده بیشتر از وزن باقیمانده است", code=403)  # noqa

        return data

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

        representation = super().to_representation(instance)
        quota = instance.quota
        assigned_org = instance.assigned_organization
        assigner_org = instance.assigner_organization
        # get org quota stat detail
        quotas_stat_amount = instance.quota.quota_amount_by_org(assigned_org)

        # get quota stat for list of pricing features
        quota_stat = quota.get_quota_stat(
            assigned_org
        ) if quota.edited_pricing_features else None

        representation['remaining_weight'] = quotas_stat_amount['remaining_weight']
        representation['distributed'] = quotas_stat_amount['quota_distributed']
        representation['warehouse_entry'] = quotas_stat_amount['inventory_received']
        representation['warehouse_balance'] = quotas_stat_amount['inventory_entry_balance']
        representation['been_sold'] = quotas_stat_amount['been_sold']

        # if quota stat exists and do not have attribute values, check !
        attribute_values = quota.attribute_values.filter(org_quota_stat=quota_stat)
        representation['attribute_values'] = product_serializers.AttributeValueSerializer(
            attribute_values if attribute_values.exists() else quota.attribute_values.filter(org_quota_stat=None),
            many=True
        ).data

        # if quota stat exists and do not have broker values, check !
        broker_values = quota.broker_values.filter(org_quota_stat=quota_stat)
        representation['brokers'] = QuotaBrokerValueSerializer(
            broker_values if broker_values.exists() else quota.broker_values.filter(org_quota_stat=None),
            many=True
        ).data

        quota_serializer = QuotaSerializer(
            quota,
            context={'org': self.context['org']}
        ).data if 'org' in self.context.keys() else QuotaSerializer(quota).data

        if quota:
            representation['quota'] = quota_serializer

        if assigned_org:
            representation['assigned_organization'] = {
                'organization': assigned_org.name,
                'id': assigned_org.id,
                # if distributor is 0 , organization has not any distribute
                'is_distributor': quota.distributions_assigned.filter(
                    assigner_organization=assigned_org
                ).count()
            }

        if assigner_org:
            representation['assigner_organization'] = {
                'organization': assigner_org.name,
                'id': assigner_org.id
            }

        if instance.parent_distribution:
            representation['parent_distribution_remaining_weight'] = instance.parent_distribution.remaining_weight

        return representation
