from django.shortcuts import render
from rest_framework.mixins import ListModelMixin, DestroyModelMixin
from rest_framework.exceptions import ParseError, PermissionDenied
from rest_framework.decorators import action
from django.db import transaction
from django.conf import settings
from rest_framework import serializers
from django.utils import timezone
from rest_framework.response import Response
from django.db.models import Sum, Prefetch

from apps.inm.models import WareHouse, MaterialBatch, MIO, MIOItem, MIOItemw, Pack
from apps.inm.serializers import (
    MaterialBatchSerializer, WareHourseSerializer, MIOListSerializer, MIOItemSerializer, MioItemAnaSerializer,
    MIODoSerializer, MIOSaleSerializer, MIOPurSerializer, MIOOtherSerializer, MIOItemCreateSerializer, 
    MaterialBatchDetailSerializer, MIODetailSerializer, MIOItemTestSerializer, MIOItemPurInTestSerializer,
    MIOItemwSerializer, MioItemDetailSerializer, PackSerializer, PackMioSerializer, MIOItemListSimpleSerializer)
from apps.inm.serializers2 import MIOItemwCreateUpdateSerializer
from apps.utils.viewsets import CustomGenericViewSet, CustomModelViewSet
from apps.inm.services import InmService
from apps.inm.services_daoru import daoru_mb, daoru_mioitem_test, daoru_mioitems
from apps.utils.mixins import (BulkCreateModelMixin, BulkDestroyModelMixin, BulkUpdateModelMixin,
                               CustomListModelMixin)
from apps.utils.orm import inflight_sum, restql_wants
from apps.utils.permission import has_perm
from .filters import MaterialBatchFilter, MioFilter
from apps.qm.serializers import FtestProcessSerializer
from apps.mtm.models import Material
from drf_yasg.utils import swagger_auto_schema
from drf_yasg import openapi
from django.db import connection
from datetime import datetime


# Create your views here.
class WarehouseVIewSet(CustomModelViewSet):
    """
    list: 仓库信息

    仓库信息
    """
    queryset = WareHouse.objects.all()
    serializer_class = WareHourseSerializer
    search_fields = ['name', 'number', 'place']
    ordering = ['create_time']

    def perform_destroy(self, instance):
        if MaterialBatch.objects.filter(warehouse=instance).exclude(count=0).exists():
            raise ParseError('该仓库存在物料')
        instance.delete()


class MaterialBatchViewSet(ListModelMixin, CustomGenericViewSet):
    """
    list: 物料批次

    物料批次
    """
    perms_map = {'get': '*'}
    queryset = MaterialBatch.objects.filter(count__gt=0)
    serializer_class = MaterialBatchSerializer
    retrieve_serializer_class = MaterialBatchDetailSerializer
    select_related_fields = ['warehouse', 'material', 'supplier']
    filterset_class = MaterialBatchFilter
    search_fields = ['material__name', 'material__number',
                     'material__model', 'material__specification', 'batch']
    ordering = ['-update_time']

    def get_queryset(self):
        queryset = super().get_queryset()
        # 可发数量(count_canmio = 存量 - 正在出入库数量)相关字段/过滤按需注解在途量,
        # 单次聚合替代逐行属性 N+1; 调用方用 ?query={...} 不取这些字段时则跳过子查询
        need = (restql_wants(self.request, ('count_mioing', 'count_canmio'))
                or 'count_canmio__gt' in self.request.query_params)
        if need:
            queryset = queryset.annotate(
                count_mioing_anno=inflight_sum(
                    MIOItem, 'mb', 'mio__submit_time__isnull', 'count',
                    max_digits=12, decimal_places=3),
            )
        return queryset

    @action(methods=['post'], detail=False, serializer_class=serializers.Serializer, perms_map={'post': 'materialbatch.daoru'})
    @transaction.atomic
    def daoru(self, request, *args, **kwargs):
        """导入物料批次

        导入物料
        """
        daoru_mb(settings.BASE_DIR + request.data.get('path', ''))
        return Response()


class MioDoViewSet(BulkCreateModelMixin, BulkUpdateModelMixin, CustomGenericViewSet):
    perms_map = {'post': '*', 'put': 'mio.do'}
    queryset = MIO.objects.filter(
        type__in=[MIO.MIO_TYPE_DO_IN, MIO.MIO_TYPE_DO_OUT])
    serializer_class = MIODoSerializer

    def create(self, request, *args, **kwargs):
        """
        生产领料/入库

        生产领料/入库
        """
        return super().create(request, *args, **kwargs)


class MioSaleViewSet(BulkCreateModelMixin, BulkUpdateModelMixin, CustomGenericViewSet):
    perms_map = {'post': '*', 'put': 'mio.sale'}
    queryset = MIO.objects.filter(
        type__in=[MIO.MIO_TYPE_DO_IN, MIO.MIO_TYPE_DO_OUT])
    serializer_class = MIOSaleSerializer

    def create(self, request, *args, **kwargs):
        """
        销售出库

        销售出库
        """
        return super().create(request, *args, **kwargs)


class MioPurViewSet(BulkCreateModelMixin, BulkUpdateModelMixin, CustomGenericViewSet):
    perms_map = {'post': '*', 'put': 'mio.pur'}
    queryset = MIO.objects.filter(
        type__in=[MIO.MIO_TYPE_PUR_IN])
    serializer_class = MIOPurSerializer

    def create(self, request, *args, **kwargs):
        """
        采购入库

        采购入库
        """
        return super().create(request, *args, **kwargs)


class MioOtherViewSet(BulkCreateModelMixin, BulkUpdateModelMixin, CustomGenericViewSet):
    perms_map = {'post': '*', 'put': 'mio.other'}
    queryset = MIO.objects.filter(
        type__in=[MIO.MIO_TYPE_OTHER_OUT, MIO.MIO_TYPE_OTHER_IN])
    serializer_class = MIOOtherSerializer

    def create(self, request, *args, **kwargs):
        """
        其他出入库

        其他出入库
        """
        return super().create(request, *args, **kwargs)


class MIOViewSet(CustomModelViewSet):
    """
    list: 出入库记录

    出入库记录
    """
    queryset = MIO.objects.all()
    select_related_fields = ['create_by', 'belong_dept', 'do_user',
                             'submit_user', 'supplier', 'order', 'customer', 'pu_order', 'mgroup']
    prefetch_related_fields = ['materials']
    serializer_class = MIOListSerializer
    retrieve_serializer_class = MIODetailSerializer
    filterset_class = MioFilter
    search_fields = ['id', 'number', 'item_mio__batch', 'item_mio__material__name', 'item_mio__material__specification', 'item_mio__material__model',
                     'item_mio__a_mioitem__batch']
    data_filter = True

    def get_queryset(self):
        queryset = super().get_queryset()
        if self.action == 'retrieve':
            queryset = queryset.prefetch_related(Prefetch(
                'item_mio',
                queryset=MIOItem.objects.select_related('material'),
            ))
        return queryset

    @classmethod
    def lock_and_check_can_update(cls, mio:MIO):
        if not connection.in_atomic_block:
            raise ParseError("请在事务中调用该方法")
        mio:MIO = MIO.objects.select_for_update().get(id=mio.id)
        if mio.submit_time is not None:
            raise ParseError("该记录已提交无法更改")
        return mio
    
    def add_info_for_list(self, data):
        # 1. 收集所有mio的ID
        mio_ids = [item['id'] for item in data]
        
        # 2. 预初始化mio字典和items列表
        mio_dict = {item['id']: {
            **item,
            'is_tested': False,  # 默认值设为False
            'mioitems': []
        } for item in data}
        
        # 3. 批量查询MIOItem数据
        if mio_ids:  # 避免空查询
            mioitems = MIOItemListSimpleSerializer(
                instance=MIOItem.objects.filter(
                    mio__id__in=mio_ids
                ).select_related("warehouse", "material"),
                many=True
            ).data
            
            # 4. 单次循环处理所有item
            for item in mioitems:
                mio_id = item['mio']
                if mio_id in mio_dict:
                    mio_dict[mio_id]['mioitems'].append(item)
                    # 更新is_tested状态（只要有一个item有test_date就为True）
                    if item.get('test_date'):
                        mio_dict[mio_id]['is_tested'] = True
        
        # 5. 直接返回原始data列表，避免额外转换
        for item in data:
            item.update(mio_dict[item['id']])
        
        return data
    
    def get_serializer_class(self):
        if self.action in ['create', 'update', 'partial_update']:
            type = self.request.data.get('type')
            user = self.request.user
            if type in [MIO.MIO_TYPE_DO_IN, MIO.MIO_TYPE_DO_OUT, MIO.MIO_TYPE_BORROW_OUT, MIO.MIO_TYPE_RETURN_IN]:
                if not has_perm(user, ['mio.do']):
                    raise PermissionDenied
                return MIODoSerializer
            elif type in [MIO.MIO_TYPE_OTHER_IN, MIO.MIO_TYPE_OTHER_OUT]:
                if not has_perm(user, ['mio.other']):
                    raise PermissionDenied
                return MIOOtherSerializer
            elif type == MIO.MIO_TYPE_SALE_OUT:
                if not has_perm(user, ['mio.sale']):
                    raise PermissionDenied
                return MIOSaleSerializer
            elif type in [MIO.MIO_TYPE_PUR_IN, MIO.MIO_TYPE_PUR_OUT]:
                if not has_perm(user, ['mio.pur']):
                    raise PermissionDenied
                return MIOPurSerializer
        return super().get_serializer_class()

    def perform_destroy(self, instance):
        if instance.state != MIO.MIO_CREATE:
            raise ParseError('非创建中不可删除')
        return super().perform_destroy(instance)

    @action(methods=['post'], detail=True, perms_map={'post': 'mio.submit'}, serializer_class=serializers.Serializer)
    @transaction.atomic
    def submit(self, request, *args, **kwargs):
        """提交

        提交
        """
        current = self.get_object()
        ins = MIO.objects.select_for_update().get(pk=current.pk)
        if ins.inout_date is None:
            raise ParseError('出入库日期未填写')
        if ins.state != MIO.MIO_CREATE:
            raise ParseError('记录状态异常')
        now = timezone.now()
        ins.submit_user = request.user
        ins.submit_time = now
        ins.update_by = request.user
        ins.state = MIO.MIO_SUBMITED
        ins.save()
        InmService.update_inm(ins)
        return Response(MIOListSerializer(instance=ins).data)

    @action(methods=['post'], detail=True, perms_map={'post': 'mio.submit'}, serializer_class=serializers.Serializer)
    @transaction.atomic
    def revert(self, request, *args, **kwargs):
        """撤回

        撤回
        """
        current = self.get_object()
        ins = MIO.objects.select_for_update().get(pk=current.pk)
        user = self.request.user
        if ins.state != MIO.MIO_SUBMITED:
            raise ParseError('记录状态异常')
        if ins.submit_user != user:
            raise ParseError('非提交人不可撤回')
        ins.submit_user = None
        ins.update_by = user
        ins.state = MIO.MIO_CREATE
        ins.submit_time = None
        ins.save()
        InmService.update_inm(ins, is_reverse=True)
        return Response()


    @action(methods=['post'], detail=True, perms_map={'post': 'mio.update'}, serializer_class=PackMioSerializer)
    @transaction.atomic
    def pack_mioitem(self, request, *args, **kwargs):
        """装箱

        装箱
        """
        mio:MIO = self.get_object()
        if mio.submit_time is not None:
            raise ParseError('该出入库已提交不可装箱')
        sr = PackMioSerializer(data=request.data)
        sr.is_valid(raise_exception=True)
        vdata = sr.validated_data
        pack_index = vdata["pack_index"]
        mioitems = vdata["mioitems"]
        if not mioitems:
            raise ParseError('未选择明细')
        for id in mioitems:
            mioitem = MIOItem.objects.get(id=id)
            if mioitem.mio != mio:
                raise ParseError('存在明细不属于该箱')
            mioitem.pack_index = pack_index
            mioitem.save(update_fields=['pack_index', 'update_time'])
        return Response()

    @action(methods=['post'], detail=True, perms_map={'post': 'mio.update'}, serializer_class=serializers.Serializer)
    def daoru_mioitem(self, request, *args, **kwargs):
        """导入明细

        导入明细
        """
        daoru_mioitems(settings.BASE_DIR + request.data.get('path', ''), mio=self.get_object())
        return Response()
class PackViewSet(CustomListModelMixin, BulkCreateModelMixin, BulkDestroyModelMixin, CustomGenericViewSet):
    """
    list: 装箱记录

    装箱记录
    """
    perms_map = {'get': '*', 'post': '*', 'delete': '*'}
    queryset = Pack.objects.all()
    serializer_class = PackSerializer
    filterset_fields = ["mio"]
    ordering = ["mio", "index"]

    @action(methods=['post'], detail=False, perms_map={'post': 'mio.update'}, serializer_class=PackMioSerializer)
    @transaction.atomic
    def pack_mioitem(self, request, *args, **kwargs):
        """装箱

        装箱
        """
        vdata = PackMioSerializer(data=request.data)
        packId = vdata["pack"]
        pack:Pack = Pack.objects.get(id=packId)
        mioitems = vdata["mioitems"]
        if not mioitems:
            raise ParseError('未选择明细')
        for id in mioitems:
            mioitem = MIOItem.objects.get(id=id)
            if mioitem.mio != pack.mio:
                raise ParseError('存在明细不属于该装箱记录')
            mioitem.pack = pack
            mioitem.save(update_fields=['pack', 'update_time'])
        return Response()
            
    

class MIOItemViewSet(CustomListModelMixin, BulkCreateModelMixin, BulkDestroyModelMixin, CustomGenericViewSet):
    """
    list: 出入库明细

    出入库明细
    """
    perms_map = {'get': '*', 'post': '*', 'delete': '*'}
    queryset = MIOItem.objects.all()
    serializer_class = MIOItemSerializer
    retrieve_serializer_class = MioItemDetailSerializer
    create_serializer_class = MIOItemCreateSerializer
    select_related_fields = ['warehouse', 'mio', 'material', 'test_user', 'wm__defect', 'mb__defect']
    filterset_fields = {
        "warehouse": ["exact"],
        "mio": ["exact"],
        "mio__state": ["exact"],
        "mio__type": ["exact", "in"],
        "mio__inout_date": ["gte", "lte", "exact"],
        "material": ["exact"],
        "material__type": ["exact"],
        "material__test_mode_in": ["exact"],
        "test_date": ["isnull", "exact"]
    }
    ordering = ['create_time']
    ordering_fields = ['create_time', 'test_date']
    search_fields =['batch', 'a_mioitem__batch']

    def add_info_for_list(self, data):
        with_mio = self.request.query_params.get('with_mio', "no")
        if with_mio == "yes" and isinstance(data, list):
            mio_ids = [item['mio'] for item in data]
            mio_qs = MIO.objects.filter(id__in=mio_ids)
            mio_qs_= MIOListSerializer(mio_qs, many=True).data
            mio_dict = {mio['id']: mio for mio in mio_qs_}
            for item in data:
                mioId = item['mio']
                item['mio_'] = mio_dict[mioId]
        return data

    @swagger_auto_schema(manual_parameters=[
        openapi.Parameter(name="with_mio", in_=openapi.IN_QUERY, description="是否返回出入库记录信息",
                          type=openapi.TYPE_STRING, required=False),
        openapi.Parameter(name="query", in_=openapi.IN_QUERY, description="定制返回数据",
                          type=openapi.TYPE_STRING, required=False),
        openapi.Parameter(name="with_children", in_=openapi.IN_QUERY, description="带有children(yes/no/count)",
                          type=openapi.TYPE_STRING, required=False)
    ])
    def list(self, request, *args, **kwargs):
        return super().list(request, *args, **kwargs)
    
    def perform_create(self, serializer):
      if isinstance(serializer.validated_data, list):
          mio_cache = {}
          for item in serializer.validated_data:
              mio = item['mio']
              if mio.id not in mio_cache:
                  mio_cache[mio.id] = MIOViewSet.lock_and_check_can_update(mio)
              item['mio'] = mio_cache[mio.id]
      else:
          serializer.validated_data["mio"] = MIOViewSet.lock_and_check_can_update(serializer.validated_data['mio'])
      return super().perform_create(serializer)

    def perform_destroy(self, instance):
        MIOViewSet.lock_and_check_can_update(instance.mio)
        if has_perm(self.request.user, ['mio.update']) is False and instance.mio.create_by != self.request.user:
            raise PermissionDenied('无权限删除')
        return super().perform_destroy(instance)

    @action(methods=['post'], detail=True, perms_map={'post': 'mio.update'}, serializer_class=serializers.Serializer)
    @transaction.atomic
    def revert_and_del(self, request, *args, **kwargs):
        """撤回并删除

        撤回并删除
        """
        ins:MIOItem = self.get_object()
        InmService.revert_and_del(ins)
        return Response()
        

    @action(methods=['post'], detail=True, perms_map={'post': 'mioitem.test'}, serializer_class=MIOItemTestSerializer)
    @transaction.atomic
    def test(self, request, *args, **kwargs):
        """半成品检验

        半成品检验
        """
        ins: MIOItem = self.get_object()
        if ins.material.tracking != Material.MA_TRACKING_BATCH:
            raise ParseError('该物料非批次管理')
        mio = ins.mio
        if ins.test_date:
            raise ParseError('该明细已检验')
        if mio.state != MIO.MIO_SUBMITED:
            raise ParseError('该出入库记录还未提交')
        sr = MIOItemTestSerializer(instance=ins, data=request.data)
        sr.is_valid(raise_exception=True)
        sr.save()
        # 开始变动库存
        InmService.update_mb_item(ins, -1, 'count_notok')
        return Response()
    
    @action(methods=['post'], detail=True, perms_map={'post': 'mioitem.test'}, serializer_class=serializers.Serializer)
    @transaction.atomic
    def test_revert(self, request, *args, **kwargs):
        """
        检验撤回
        """
        ins: MIOItem = self.get_object()
        if ins.test_date is None:
            raise ParseError('该明细还未检验')
        if ins.count_notok > 0:
            InmService.update_mb_item(ins, 1, 'count_notok')
        elif ins.count_notok == 0:
            pass
        ins.test_date = None
        ins.save()
        return Response()

    @action(methods=['post'], detail=True, perms_map={'post': 'mioitem.test'}, serializer_class=MIOItemPurInTestSerializer)
    @transaction.atomic
    def test_pur_in(self, request, *args, **kwargs):
        """入厂检验

        入厂检验
        """
        ins: MIOItem = self.get_object()
        if ins.material.tracking != Material.MA_TRACKING_BATCH:
            raise ParseError('该物料非批次管理')
        if ins.test_date:
            raise ParseError('该明细已检验')
        sr = MIOItemPurInTestSerializer(instance=ins, data=request.data)
        sr.is_valid(raise_exception=True)
        sr.save()
        return Response()

    @action(methods=['post'], detail=False, perms_map={'post': '*'}, serializer_class=MioItemAnaSerializer)
    def sale_out_ana(self, request, *args, **kwargs):
        """交付统计数据

        交付统计数据
        """
        sr = MIOItemTestSerializer(data=request.data)
        sr.is_valid(raise_exception=True)
        vdata = sr.validated_data
        mioitems = MIOItem.objects.filter(
            mio__type='sale_out', mio__state=MIO.MIO_SUBMITED)
        if vdata.get('material_cate'):
            mioitems = mioitems.filter(
                material__cate=vdata['material_cate'])
        if vdata.get('start_date', ''):
            mioitems = mioitems.filter(
                mio__inout_date__gte=vdata['start_date'])
        if vdata.get('end_date', ''):
            mioitems = mioitems.filter(mio__inout_date__lte=vdata['end_date'])
        res = mioitems.aggregate(count=Sum('count'))
        for i in res:
            if res[i] is None:
                res[i] = 0
        return Response(res)
    
    @action(methods=['post'], detail=True, perms_map={'post': 'mio.update'}, serializer_class=serializers.Serializer)
    @transaction.atomic
    def test_daoru_bg(self, request, *args, **kwargs):
        """导入棒管检验

        导入棒管检验
        """
        daoru_mioitem_test(path=settings.BASE_DIR + request.data.get('path', ''), mioitem=self.get_object())
        return Response()


class MIOItemwViewSet(CustomModelViewSet):
    perms_map = {'get': '*', 'post': 'mio.update', 'put': 'mio.update', 'delete': 'mio.update'}
    queryset = MIOItemw.objects.all()
    serializer_class = MIOItemwCreateUpdateSerializer
    filterset_fields = {
        'mioitem': ['exact'],
        'mioitem__material__type': ['exact'],
        "wpr": ['exact'],
        "number": ["exact"],
        "ftest": ["isnull"],
        "mioitem__mio__state": ["exact"]
    }
    select_related_fields = ["ftest"]
    ordering = ["number", "create_time"]
    ordering_fields = ["number", "create_time"]

    def filter_queryset(self, queryset):
        if not self.detail:
            if not self.request.query_params.get('mioitem', None):
                if "ftest__isnull" in self.request.query_params:
                    pass
                else:
                    raise ParseError('请指定所属出入库记录明细')
        return super().filter_queryset(queryset)

    def cal_mioitem_count(self, mioitem:MIOItem):
        count = MIOItemw.objects.filter(mioitem=mioitem).count()
        mioitem.count = count
        mioitem.count_tested = MIOItemw.objects.filter(mioitem=mioitem, ftest__isnull=False).count()
        mioitem.count_notok = MIOItemw.objects.filter(mioitem=mioitem, ftest__is_ok=False).count()
        if mioitem.test_date is None:
            mioitem.test_date = datetime.now()
        if mioitem.test_user is None:
            mioitem.test_user = self.request.user
        mioitem.save()

    def perform_create(self, serializer):
        MIOViewSet.lock_and_check_can_update(serializer.validated_data['mioitem'].mio)
        ins:MIOItemw = serializer.save()
        self.cal_mioitem_count(ins.mioitem)

    def perform_update(self, serializer):
        ins:MIOItemw = serializer.instance
        MIOViewSet.lock_and_check_can_update(ins.mioitem.mio)
        ins:MIOItemw = serializer.save()
        self.cal_mioitem_count(ins.mioitem)

    def perform_destroy(self, instance: MIOItemw):
        mioitem = instance.mioitem
        MIOViewSet.lock_and_check_can_update(mioitem.mio)
        ftest = instance.ftest
        instance.delete()
        if ftest:
            ftest.delete()
        self.cal_mioitem_count(mioitem)
