from rest_framework import serializers
from rest_framework.exceptions import ValidationError, ParseError

from apps.mtm.serializers import MaterialSerializer
from apps.pum.models import PuOrder
from apps.sam.models import Order
from apps.system.models import Dept, User
from apps.utils.constants import EXCLUDE_FIELDS_BASE, EXCLUDE_FIELDS_DEPT, EXCLUDE_FIELDS
from apps.utils.serializers import CustomModelSerializer
from apps.mtm.models import Material
from .models import MIO, MaterialBatch, MIOItem, WareHouse, MIOItemA, MaterialBatchA, MIOItemw, Pack
from django.db import transaction
from django.db.models import F, Sum, DecimalField
from server.settings import get_sysconfig
from apps.wpmw.models import Wpr
from decimal import Decimal


class WareHourseSerializer(CustomModelSerializer):
    class Meta:
        model = WareHouse
        fields = '__all__'
        read_only_fields = EXCLUDE_FIELDS_DEPT


class MaterialBatchAListSerializer(CustomModelSerializer):
    material_ = MaterialSerializer(source='material', read_only=True)

    class Meta:
        model = MaterialBatchA
        fields = ['material', 'batch', 'rate', 'mb', 'id', 'material_']


class MaterialBatchAListSerializer2(CustomModelSerializer):
    material_name = serializers.StringRelatedField(
        source='material', read_only=True)

    class Meta:
        model = MaterialBatchA
        fields = ['material', 'batch', 'rate', 'mb',
                  'id', 'material_name']

class MaterialBatchSerializer(CustomModelSerializer):
    warehouse_name = serializers.CharField(
        source='warehouse.name', read_only=True)
    material_name = serializers.StringRelatedField(
        source='material', read_only=True)
    supplier_name = serializers.StringRelatedField(
        source='supplier', read_only=True)
    material_ = MaterialSerializer(source='material', read_only=True)
    defect_name = serializers.CharField(source="defect.name", read_only=True)
    count_mioing = serializers.SerializerMethodField(label='正在出入库数量')

    class Meta:
        model = MaterialBatch
        fields = '__all__'
        read_only_fields = EXCLUDE_FIELDS_BASE

    def get_count_mioing(self, instance):
        # 列表接口的 queryset 已注解 count_mioing_anno(单次聚合); 嵌套等无注解场景回退模型属性
        # 保留 decimal 精度(原 IntegerField 会截断在途量, 导致可发量偏大)
        return instance.count_mioing_anno if hasattr(instance, 'count_mioing_anno') else instance.count_mioing

    def to_representation(self, instance):
        ret = super().to_representation(instance)
        if 'count' in ret:
            # 优先用已输出字段, 否则用注解值; 都没有(调用方用 query 关闭了该组字段)则不算, 避免误触发 N+1
            if 'count_mioing' in ret:
                mioing = ret['count_mioing']
            elif hasattr(instance, 'count_mioing_anno'):
                mioing = instance.count_mioing_anno
            else:
                mioing = None
            if mioing is not None:
                ret['count_canmio'] = str(Decimal(ret['count']) - Decimal(mioing))
        return ret


class MaterialBatchDetailSerializer(CustomModelSerializer):
    warehouse_name = serializers.CharField(
        source='warehouse.name', read_only=True)
    material_name = serializers.CharField(
        source='material.name', read_only=True)
    material_ = MaterialSerializer(source='material', read_only=True)
    assemb = MaterialBatchAListSerializer(
        source='a_mb', read_only=True, many=True)
    supplier_name = serializers.StringRelatedField(
        source='supplier', read_only=True)

    class Meta:
        model = MaterialBatch
        fields = '__all__'


class MIOListSerializer(CustomModelSerializer):
    create_by_name = serializers.CharField(
        source='create_by.name', read_only=True)
    submit_user_name = serializers.CharField(
        source='submit_user.name', read_only=True)
    do_user_name = serializers.CharField(
        source='do_user.name', read_only=True)
    mio_user_name = serializers.CharField(
        source='mio_user.name', read_only=True)
    belong_dept_name = serializers.CharField(
        source='belong_dept.name', read_only=True)
    mgroup_name = serializers.CharField(source='mgroup.name', read_only=True)
    supplier_name = serializers.CharField(
        source='supplier.name', read_only=True)
    customer_name = serializers.CharField(
        source='customer.name', read_only=True)
    order_number = serializers.CharField(source='order.number', read_only=True)
    pu_order_number = serializers.CharField(
        source='pu_order.number', read_only=True)
    total_price = serializers.SerializerMethodField(label='总价格')

    class Meta:
        model = MIO
        fields = '__all__'
        read_only_fields = EXCLUDE_FIELDS + \
            ['state', 'submit_time', 'submit_user', 'number']

    def get_total_price(self, obj):
        res = MIOItem.objects.filter(mio=obj, unit_price__isnull=False).aggregate(
            total=Sum(F('count') * F('unit_price'),
                      output_field=DecimalField(max_digits=18, decimal_places=4)))
        return res['total']


class MIOItemACreateSerializer(CustomModelSerializer):
    class Meta:
        model = MIOItemA
        fields = ['material', 'batch', 'zhuidu',
                  'b_zuidawaijing', 'g_zuidaneijing']

class MIOItemwSerializer(CustomModelSerializer):
    class Meta:
        model = MIOItemw
        fields = "__all__"
        read_only_fields = EXCLUDE_FIELDS_BASE + ["mioitem"]


class MIOItemCreateSerializer(CustomModelSerializer):
    assemb = MIOItemACreateSerializer(
        label='组合件信息', many=True, write_only=True, required=False)
    mioitemw = MIOItemwSerializer(many=True, required=False)

    class Meta:
        model = MIOItem
        fields = ['mio', 'warehouse', 'material',
                  'batch', 'count', 'assemb', 'is_testok', 'mioitemw', 'mb', 'wm', 'unit_price', 'note', "pack_index", "count_send"]
        extra_kwargs = {
            'mio': {'required': True}, 'warehouse': {'required': False},
            'material': {'required': False}, 'batch': {'required': False, "allow_null": True, "allow_blank": True}}


    def create(self, validated_data):
        mio:MIO = validated_data['mio']
        mio_type = mio.type
        mb = validated_data.get('mb', None)
        wm = validated_data.get('wm', None)
        assemb = validated_data.pop('assemb', [])
        if mio.type == MIO.MIO_TYPE_DO_IN and not assemb:
            if not wm:
                raise ParseError('生产入库必须指定车间库存')
        elif mio.type == MIO.MIO_TYPE_DO_OUT:
            if not mb:
                raise ParseError('生产领料必须指定仓库库存')
        if mb:
            validated_data["material"] = mb.material
            validated_data["batch"] = mb.batch
            validated_data["warehouse"] = mb.warehouse
        elif wm:
            validated_data["material"] = wm.material
            validated_data["batch"] = wm.batch

        material: Material = validated_data['material']
        batch = validated_data.get("batch", None)
        if not batch:
            batch = "无"
        if batch != '无' and len(batch) < 5:
            raise ParseError('批次号格式错误')
        if material.is_hidden:
            raise ParseError('隐式物料不可出入库')
        if mio.type in [MIO.MIO_TYPE_RETURN_IN, MIO.MIO_TYPE_BORROW_OUT]:
            if not material.into_wm:
                raise ParseError('该物料不可领用或归还')

        if mio.state != MIO.MIO_CREATE:
            raise ParseError('出入库记录非创建中不可新增')
        # 生产领料要校验是否进行检验
        # 某些客户此处无需校验(全局总开关); 免检物料单独放行
        check_test_when_do_out = get_sysconfig('mes.check_test_when_do_out', True)
        if (check_test_when_do_out and mio.type == MIO.MIO_TYPE_DO_OUT
                and material.test_mode_in != Material.MA_TEST_EXEMPT):
            mis = MIOItem.objects.filter(batch=batch, material=material, mio__type__in=[MIO.MIO_TYPE_PUR_IN, MIO.MIO_TYPE_DO_IN, MIO.MIO_TYPE_OTHER_IN])
            if mis.exists() and (not mis.exclude(test_date=None).exists()):
                raise ParseError('该批次的物料未经检验')

        count = validated_data["count"]
        batch = validated_data["batch"]
        # 未给定单价时用物料主数据单价固化到明细上
        if validated_data.get('unit_price') is None and material.unit_price is not None:
            validated_data['unit_price'] = material.unit_price
        mioitemw = validated_data.pop('mioitemw', [])
        instance:MIOItem = super().create(validated_data)
        assemb_dict = {}
        for i in assemb:
            assemb_dict[i['material'].id] = i
        if material.is_assemb and '_in' in mio.type:  # 仅入库且是组合件的时候需要填写下一级
            components = material.components
            for k, v in components.items():
                if k in assemb_dict:
                    mia = assemb_dict[k]
                    MIOItemA.objects.create(
                        mioitem=instance, rate=v, **mia)
                else:
                    raise ParseError('缺少组合件')
        if material.tracking == Material.MA_TRACKING_SINGLE:
            if len(mioitemw) == 0:
                if mb:
                    wpr_qs = Wpr.get_qs_by_mb(mb)
                    if wpr_qs.count() == validated_data["count"]:
                        for item in wpr_qs:
                            MIOItemw.objects.create(mioitem=instance, number=item.number, wpr=item)
                    else:
                        raise ParseError('请提供产品明细编号')
                elif wm:
                    wpr_qs = Wpr.get_qs_by_wm(wm)
                    if wpr_qs.count() == validated_data["count"]:
                        for item in wpr_qs:
                            MIOItemw.objects.create(mioitem=instance, number=item.number, wpr=item)
                    else:
                        raise ParseError('请提供产品明细编号')
                elif mio.type in [MIO.MIO_TYPE_PUR_IN, MIO.MIO_TYPE_OTHER_IN] and count==1:
                    MIOItemw.objects.create(mioitem=instance, number=batch)
                else:
                    raise ParseError('不支持自动生成请提供产品明细')
            elif len(mioitemw) >= 1:
                mio_type = mio.type
                if mio_type != "pur_in" and mio_type != "other_in":
                    wprIds = [i["wpr"].id for i in mioitemw]
                    if instance.mb:
                        mb_ids = list(Wpr.objects.filter(id__in=wprIds).values_list("mb__id", flat=True).distinct())
                        if len(mb_ids) == 1 and mb_ids[0] == instance.mb.id:
                            pass
                        else:
                            raise ParseError(f'{batch}物料明细中存在{len(mb_ids)}个不同物料批次')
                    elif instance.wm:
                        wm_ids = list(Wpr.objects.filter(id__in=wprIds).values_list("wm__id", flat=True).distinct())
                        if len(wm_ids) == 1 and wm_ids[0] == instance.wm.id:
                            pass
                        else:
                            raise ParseError(f'{batch}物料明细中存在{len(wm_ids)}个不同物料批次')
                for item in mioitemw:
                    if item.get("wpr", None) is None and mio_type != "pur_in" and mio_type != "other_in":
                        raise ParseError(f'{item["number"]}_请提供产品明细ID')
                    elif item.get("number_out", None) is not None and mio_type != MIO.MIO_TYPE_SALE_OUT:
                        raise ParseError(f'{item["number"]}_非销售出库不可赋予产品对外编号')
                    else:
                        MIOItemw.objects.create(mioitem=instance, **item)
        return instance


class MIOItemAListSerializer(CustomModelSerializer):
    material_ = MaterialSerializer(source='material', read_only=True)
    material_name = serializers.StringRelatedField(
        source='material', read_only=True)

    class Meta:
        model = MIOItemA
        fields = "__all__"
        read_only_fields = EXCLUDE_FIELDS_BASE


class MIOItemListSimpleSerializer(CustomModelSerializer):
    warehouse_name = serializers.CharField(source='warehouse.name', read_only=True)
    material_name = serializers.StringRelatedField(
        source='material', read_only=True)

    class Meta:
        model = MIOItem
        fields = ["id", "mio", "material", "warehouse", "material_name", "warehouse_name", "batch", "count", "test_date", "count_notok"]


class _MioDetailItemMaterialSerializer(CustomModelSerializer):
    class Meta:
        model = Material
        fields = ["id", "name", "model"]


class MIOItemForMioDetailSerializer(CustomModelSerializer):
    """MIO 详情下挂的 items 用,仅含前端实际消费的字段"""
    material_ = _MioDetailItemMaterialSerializer(source='material', read_only=True)

    class Meta:
        model = MIOItem
        fields = ["id", "mio", "material", "material_", "batch", "count", "pack_index"]

class MIOItemSerializer(CustomModelSerializer):
    warehouse_name = serializers.CharField(source='warehouse.name', read_only=True)
    material_ = MaterialSerializer(source='material', read_only=True)
    assemb = serializers.SerializerMethodField(label="组合件信息")
    material_name = serializers.StringRelatedField(
        source='material', read_only=True)
    inout_date = serializers.DateField(source='mio.inout_date', read_only=True)
    test_user_name = serializers.CharField(source='test_user.name', read_only=True)
    mioitemw = MIOItemwSerializer(many=True, required=False)
    defect = serializers.SerializerMethodField(label="缺陷")
    defect_name = serializers.SerializerMethodField(label="缺陷名称")
    okcate = serializers.SerializerMethodField(label="缺陷分类")

    class Meta:
        model = MIOItem
        fields = '__all__'

    def _resolve_defect(self, obj):
        if obj.wm_id and obj.wm.defect_id:
            return obj.wm.defect
        if obj.mb_id and obj.mb.defect_id:
            return obj.mb.defect
        return None

    def get_defect(self, obj):
        d = self._resolve_defect(obj)
        return d.id if d else None

    def get_defect_name(self, obj):
        d = self._resolve_defect(obj)
        return d.name if d else None

    def get_okcate(self, obj):
        d = self._resolve_defect(obj)
        return d.okcate if d else None

    def to_representation(self, instance):
        ret = super().to_representation(instance)
        ret["price"] = None
        if ret["unit_price"] is not None:
            ret["price"] = Decimal(ret["count"]) * Decimal(ret["unit_price"])
        return ret
    
    def get_assemb(self, obj):
        qs = MIOItemA.objects.filter(mioitem=obj)
        if qs.exists():
            return MIOItemAListSerializer(qs, many=True).data
        elif obj.mb and obj.mb.material.is_assemb:
            return MaterialBatchAListSerializer2(MaterialBatchA.objects.filter(mb=obj.mb), many=True).data
        return None
        
    
class MioItemDetailSerializer(MIOItemSerializer):
    mio_ = MIOListSerializer(source='mio', read_only=True)


class MIODoSerializer(CustomModelSerializer):
    belong_dept = serializers.PrimaryKeyRelatedField(
        label="车间", queryset=Dept.objects.all(), required=False)
    do_user = serializers.PrimaryKeyRelatedField(
        label="执行人", queryset=User.objects.all(), required=True)
    note = serializers.CharField(
        required=False, allow_blank=True)

    class Meta:
        model = MIO
        fields = ['id', 'number', 'note', 'do_user',
                  'belong_dept', 'type', 'inout_date', 'mgroup', 'mio_user', 'type']
        extra_kwargs = {'inout_date': {'required': True}, 
                        'do_user': {'required': True}, 
                        'number': {"required": False, "allow_blank": True},
                        'type': {'required': True}}

    def validate(self, attrs):
        if 'mgroup' in attrs and attrs['mgroup']:
            attrs['belong_dept'] = attrs['mgroup'].belong_dept
        if not attrs.get("belong_dept", None):
            raise ParseError('请选择车间或工段')
        return attrs
    
    def create(self, validated_data):
        type = validated_data['type']
        if type in [MIO.MIO_TYPE_DO_IN, MIO.MIO_TYPE_DO_OUT, MIO.MIO_TYPE_BORROW_OUT, MIO.MIO_TYPE_RETURN_IN]:
            pass
        else:
            raise ValidationError('出入库类型错误')
        if not validated_data.get("number", None):
            validated_data["number"] = MIO.get_a_number(validated_data["type"])
        return super().create(validated_data)

    def update(self, instance, validated_data):
        validated_data.pop('type', None)
        return super().update(instance, validated_data)


class MIOSaleSerializer(CustomModelSerializer):
    # order = serializers.PrimaryKeyRelatedField(
    #     label="订单", queryset=Order.objects.all(), required=True)
    note = serializers.CharField(
        required=False, allow_blank=True)

    class Meta:
        model = MIO
        fields = ['id', 'number', 'note', 'order', 'inout_date', 'customer', 'mio_user']
        extra_kwargs = {'inout_date': {'required': True}, 'number': {"required": False, "allow_blank": True}}

    def create(self, validated_data):
        validated_data['type'] = MIO.MIO_TYPE_SALE_OUT
        order: Order = validated_data.get('order', None)
        if not validated_data.get("number", None):
            validated_data["number"] = MIO.get_a_number(validated_data["type"])
        if order:
            if order.state in [Order.ORDER_CREATE, Order.ORDER_DELIVERED]:
                raise ValidationError('销售订单状态错误')
            validated_data['customer'] = order.customer
            if order.belong_dept:
                validated_data['belong_dept'] = order.belong_dept
        return super().create(validated_data)

    def update(self, instance, validated_data):
        validated_data.pop('type', None)
        return super().update(instance, validated_data)


class MIOPurSerializer(CustomModelSerializer):
    # pu_order = serializers.PrimaryKeyRelatedField(
    #     label="采购订单", queryset=PuOrder.objects.all(), required=True)
    note = serializers.CharField(
        required=False, allow_blank=True)

    class Meta:
        model = MIO
        fields = ['id', 'number', 'note', 'pu_order', 'inout_date', 'supplier', 'mio_user', 'type']
        extra_kwargs = {'inout_date': {'required': True}, 
                        'number': {"required": False, "allow_blank": True},
                        'type': {'required': True}}

    def create(self, validated_data):
        type = validated_data["type"]
        if type in [MIO.MIO_TYPE_PUR_IN, MIO.MIO_TYPE_PUR_OUT]:
            pass
        else:
            raise ValidationError('出入库类型错误')
        if not validated_data.get("number", None):
            validated_data["number"] = MIO.get_a_number(validated_data["type"])
        pu_order: PuOrder = validated_data.get('pu_order', None)
        if pu_order:
            if pu_order.state in [PuOrder.PUORDER_CREATE, PuOrder.PUORDER_DONE]:
                raise ValidationError('采购订单状态错误')
            validated_data['supplier'] = pu_order.supplier
            if pu_order.belong_dept:
                validated_data['belong_dept'] = pu_order.belong_dept
        return super().create(validated_data)

    def update(self, instance, validated_data):
        validated_data.pop('type', None)
        return super().update(instance, validated_data)


class MIOOtherSerializer(CustomModelSerializer):
    note = serializers.CharField(
        required=False, allow_blank=True)

    class Meta:
        model = MIO
        fields = ['id', 'number', 'note', 'supplier',
                  'customer', 'type', 'inout_date', 'mio_user']
        extra_kwargs = {'inout_date': {'required': True}, 'number': {"required": False, "allow_blank": True}}

    def create(self, validated_data):
        if not validated_data.get("number", None):
            validated_data["number"] = MIO.get_a_number(validated_data["type"])
        if validated_data['type'] not in [MIO.MIO_TYPE_OTHER_OUT, MIO.MIO_TYPE_OTHER_IN]:
            raise ValidationError('出入库类型错误')
        return super().create(validated_data)

    def update(self, instance, validated_data):
        validated_data.pop('type', None)
        return super().update(instance, validated_data)


class MIODetailSerializer(MIOListSerializer):
    items = MIOItemForMioDetailSerializer(source='item_mio', many=True, read_only=True)


class MIOItemTestSerializer(CustomModelSerializer):
    class Meta:
        model = MIOItem
        fields = ['id', 'test_date', 'test_user', 'count_notok', 'count_n_zw', 'count_n_tw',
                  'count_n_qp', 'count_n_wq', 'count_n_dl', 'count_n_pb', 'count_n_dxt', 'count_n_js', 'count_n_qx', 'count_n_zz', 'count_n_ysq', 'count_n_qt', 'count_n_hs', 'count_n_b', 'test_note']
        extra_kwargs = {
            'test_date': {'required': True},
            'test_user': {'required': True}
        }

        def validate(self, attrs):
            count_notok = 0
            for i in attrs:
                if 'count_n_' in i:
                    count_notok = count_notok + attrs[i]
            attrs['count_notok'] = count_notok
            return attrs

class MioItemAnaSerializer(serializers.Serializer):
    start_date = serializers.DateField(label='开始日期', required=True)
    end_date = serializers.DateField(label='结束日期', required=True)
    material_cate = serializers.CharField(label='物料系列', required=True)


class MIOItemPurInTestSerializer(CustomModelSerializer):
    class Meta:
        model = MIOItem
        fields = ['id', 'test_date', 'test_user',
                  'count_bag', 'count_sampling', 'weight_kgs', 'is_testok', 'test_note']
        extra_kwargs = {
            'test_date': {'required': True},
            'test_user': {'required': True}
        }

    def validate(self, attrs):
        if 'count_sampling' in attrs and attrs['count_sampling']:
            pass
        else:
            weight_kgs = attrs['weight_kgs']
            attrs['weight_kgs'] = [float(i) for i in weight_kgs]
            attrs['count_sampling'] = len(attrs['weight_kgs'])
        return super().validate(attrs)


class PackSerializer(CustomModelSerializer):
    class Meta:
        model = Pack
        fields = "__all__"
        read_only_fields = EXCLUDE_FIELDS_BASE
    
    def create(self, validated_data):
        index = validated_data["index"]
        mio = validated_data["mio"]
        if Pack.objects.filter(mio=mio, index=index).exists():
            raise ParseError('包装箱已存在')
        return super().create(validated_data)


class PackMioSerializer(serializers.Serializer):
    mioitems = serializers.ListField(child=serializers.CharField(), label="明细ID")
    pack_index = serializers.IntegerField(label="包装箱序号")
    # pack = serializers.CharField(label="包装箱ID")