from django.shortcuts import render
from django.conf import settings
from rest_framework.decorators import action
from rest_framework.mixins import ListModelMixin
from rest_framework.response import Response
from rest_framework.exceptions import ParseError

from apps.mtm.filters import GoalFilter, MaterialFilter, RouteFilter
from apps.mtm.models import Goal, Material, Mgroup, Shift, Team, Process, Route, TeamMember, RoutePack, Srule, RouteMat
from apps.mtm.serializers import (GoalSerializer, MaterialSerializer,
                                  MgroupGoalYearSerializer, MgroupSerializer, MgroupDaysSerializer,
                                  ShiftSerializer, TeamSerializer, ProcessSerializer, 
                                  RouteSerializer, TeamMemberSerializer, RoutePackSerializer,
                                  SruleSerializer, RouteMatSerializer, RoutePackCopySerializer, MaterialExportSerializer)
from apps.mtm.services import get_mgroup_goals, daoru_material, get_mgroup_days
from apps.utils.viewsets import CustomGenericViewSet, CustomModelViewSet
from apps.utils.mixins import BulkCreateModelMixin, BulkDestroyModelMixin, CustomListModelMixin
from rest_framework.serializers import Serializer
from django.db import transaction
from apps.wf.models import Ticket
from django.utils import timezone
from rest_framework.permissions import IsAdminUser
from apps.utils.export import export_excel
from operator import itemgetter
from django.db.models import Sum, Q, Value, F, ExpressionWrapper, DecimalField
from django.db.models.functions import Coalesce

# Create your views here.
class MaterialViewSet(CustomModelViewSet):
    """
    list:产品

    产品
    """
    queryset = Material.objects.all()
    serializer_class = MaterialSerializer
    filterset_class = MaterialFilter
    search_fields = ['name', 'code', 'number', 'specification', 'model', 'bin_number_main']
    select_related_fields = ['process']
    ordering = ['name', 'model', 'specification',
                'type', 'process', 'process__sort', 'sort', 'id', 'number']
    ordering_fields = ['name', 'model', 'specification',
                       'type', 'process', 'process__sort', 'sort', 'id', 'number', 'create_time']
    
    def get_queryset(self):
        qs = super().get_queryset()
        if self.action in ["list", "retrieve"] and self.request.query_params.get('wiith_count', None) == 'yes':
            return Material.annotate_count(qs)
        return qs
    
    def perform_destroy(self, instance):
        from apps.inm.models import MaterialBatch
        if MaterialBatch.objects.filter(material=instance).exists():
            raise ParseError('该物料有库存!')
        if Route.objects.filter(Q(material=instance) | Q(material_in=instance) | Q(material_out=instance)).exists():
            raise ParseError('该物料有工艺路线!')
        instance.delete()
        Material.objects.filter(parent=instance).update(is_deleted=True)

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

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

    @action(methods=['put'], detail=True, serializer_class=Serializer, perms_map={'put': '*'})
    @transaction.atomic
    def set_week_esitimate_consume(self, request, *args, **kwargs):
        """设置周预估消耗量

        设置周预估消耗量
        """
        obj: Material = self.get_object()
        obj.week_esitimate_consume = request.data.get(
            'week_esitimate_consume', None)
        obj.save()
        return Response()

    @action(methods=['put'], detail=True, serializer_class=Serializer, perms_map={'put': '*'})
    @transaction.atomic
    def set_safe(self, request, *args, **kwargs):
        """设置安全库存上下限

        count_safe 为下限, count_safe_upper 为上限
        """
        obj: Material = self.get_object()
        obj.count_safe = request.data.get('count_safe', None)
        obj.count_safe_upper = request.data.get('count_safe_upper', None)
        obj.save(update_fields=['count_safe', 'count_safe_upper', 'update_time'])
        return Response()

    @action(methods=['get'], detail=False, serializer_class=Serializer, perms_map={'get': '*'})
    def cates(self, request, *args, **kwargs):
        res = Material.objects.exclude(cate='').exclude(cate=None).values_list('cate', flat=True).distinct()
        return Response(set(res))
    
    @action(methods=['get'], detail=False, perms_map={'get': '*'})
    def export_excel(self, request, pk=None):
        """导出excel
        导出excel
        """
        field_data = ['大类', '物料编号', '名称', '规格', '型号', '计量单位', '仓库位号', "安全库存", "单价"]
        queryset = self.filter_queryset(self.get_queryset())
        if queryset.count() > 1000:
            raise ParseError('数据量超过1000,请筛选后导出')
        odata = MaterialExportSerializer(queryset, many=True).data
        # 处理数据
        field_keys = ['cate', 'number', 'name', 'specification', 'model', 'unit', 
              'bin_number_main', 'count_safe', 'unit_price']
        getter = itemgetter(*field_keys)
        data = [list(getter(item)) for item in odata]
        return Response({'path': export_excel(field_data, data, '物料清单')})

class ShiftViewSet(CustomModelViewSet):
    """
    list:班次

    班次
    """
    queryset = Shift.objects.all()
    serializer_class = ShiftSerializer
    search_fields = ['name', 'rule']
    filterset_fields = ['rule']
    ordering = ['rule', 'sort', 'id']


class MgroupViewSet(CustomModelViewSet):
    """
    list:测点集

    测点集
    """
    queryset = Mgroup.objects.all()
    serializer_class = MgroupSerializer
    select_related_fields = ['create_by', 'belong_dept', 'process']
    filterset_fields = {
        "belong_dept": ["exact"],
        "process": ["exact"],
        "cate": ["exact"],
        "belong_dept__name": ["exact", "contains"],
        "name": ["exact", "contains"],
        "code": ["exact", "in", "isnull"]
    }
    search_fields = ['name']
    ordering = ['sort', 'create_time']

    def perform_destroy(self, instance):
        Route.objects.filter(
            mgroup=instance, material__need_route=False).delete()
        Route.objects.filter(mgroup=instance).update(mgroup=None)
        instance.delete()
    
    @action(methods=['post'], detail=False, perms_map={'post': '*'}, serializer_class=MgroupDaysSerializer)
    def get_days(self, request):
        """
        获取所属班年月日

        给定一个时间获取所属班年月日
        """
        data = request.data
        sr = MgroupDaysSerializer(data=data)
        sr.is_valid(raise_exception=True)
        vdata = sr.validated_data
        mgroup = Mgroup.objects.get(id=vdata["mgroup"])
        return Response(get_mgroup_days(mgroup, vdata.get("now", None)))




class TeamViewSet(CustomModelViewSet):
    """
    list:班组

    班组
    """
    queryset = Team.objects.all()
    serializer_class = TeamSerializer
    select_related_fields = ['belong_dept', 'leader']
    filterset_fields = ['belong_dept']
    search_fields = ['rule']


class TeamMemberViewSet(ListModelMixin, BulkCreateModelMixin, BulkDestroyModelMixin, CustomGenericViewSet):
    """
    list: 班组成员

    班组成员
    """
    perms_map = {'get': '*', 'post': 'team.update', 'delete': 'team.delete'}
    queryset = TeamMember.objects.all()
    serializer_class = TeamMemberSerializer
    select_related_fields = ['mgroup', 'post', 'team', 'user']
    filterset_fields = ['mgroup', 'post', 'team']


class GoalViewSet(CustomModelViewSet):
    """
    list: 目标

    目标
    """
    queryset = Goal.objects.all()
    serializer_class = GoalSerializer
    select_related_fields = ['mgroup', 'goal_cate']
    filterset_class = GoalFilter
    search_fields = ['name']
    ordering = ['mgroup__sort', 'goal_cate__sort', 'goal_cate__create_time']

    @action(methods=['post'], detail=False, perms_map={'post': '*'}, serializer_class=MgroupGoalYearSerializer)
    def mgroup_goals_year(self, request, pk=None):
        """
        获取工段全年目标

        获取工段全年目标
        """
        sr = MgroupGoalYearSerializer(data=request.data)
        sr.is_valid(raise_exception=True)
        vdata = sr.validated_data
        res = get_mgroup_goals(vdata['mgroup'], vdata['year'], True)
        return Response(res)


class ProcessViewSet(CustomModelViewSet):
    queryset = Process.objects.all()
    serializer_class = ProcessSerializer
    select_related_fields = ['belong_dept']
    search_fields = ['name', 'cate']
    filterset_fields = {
        "cate": ['exact'],
        "parent": ['isnull', "exact"]
    }
    ordering = ['sort', 'create_time']

    def perform_destroy(self, instance):
        if Route.objects.filter(process=instance).exists():
            raise ParseError('存在使用的工艺路线!')
        return super().perform_destroy(instance)



class RoutePackViewSet(CustomModelViewSet):
    """
    list: 工艺路线包

    工艺路线包
    """
    queryset = RoutePack.objects.all()
    serializer_class = RoutePackSerializer
    search_fields = ['name']
    select_related_fields = ['material', 'create_by', "document"]
    filterset_fields = ['material', 'state']

    def update(self, request, *args, **kwargs):
        obj: RoutePack = self.get_object()
        if obj.state != RoutePack.RP_S_CREATE:
            raise ParseError('该状态下不可编辑')
        return super().update(request, *args, **kwargs)

    @transaction.atomic
    def destroy(self, request, *args, **kwargs):
        from apps.wpm.models import Mlog
        obj: RoutePack = self.get_object()
        if obj.state != RoutePack.RP_S_CREATE:
            raise ParseError('该状态下不可删除')
        if Mlog.objects.filter(route__routepack=obj).exists():
            raise ParseError('该工艺路线包有生产记录,不可删除')
        obj.delete()
        Ticket.objects.filter(ticket_data__t_id=obj.id, ticket_data__t_model='routepack').delete()
        return Response(status=204)
    
    @transaction.atomic
    @action(methods=['post'], detail=False, perms_map={'post': 'routepack.create'}, serializer_class=RoutePackCopySerializer)
    def copy(self, request, *args, **kwargs):
        """复制工艺路线

        复制工艺路线
        """
        data = request.data
        sr = RoutePackCopySerializer(data=data)
        sr.is_valid(raise_exception=True)
        vdata = sr.validated_data
        user = request.user
        now = timezone.now()
        new_name = vdata["new_name"]
        rp = RoutePack.objects.get(id=vdata["routepack"])
        matin = Material.objects.get(id=vdata["material_in"])
        matout = Material.objects.get(id=vdata["material_out"])
        obj_c = RoutePack()
        obj_c.name = new_name
        obj_c.material = matout
        obj_c.create_by = user
        obj_c.create_time = now
        obj_c.save()
        genM = {}
        for ind, route in enumerate(Route.get_routes(routepack=rp)):
            route_new = Route()
            process = route.process
            for f in Route._meta.fields:
                if f.name in ['process', 'sort', 'is_autotask', 'is_count_utask', 'out_rate', 'div_number', 'hour_work', 'batch_bind']:
                    setattr(route_new, f.name, getattr(route, f.name, None))
            route_new.material = matout
            # material_out = RouteSerializer.gen_material_out(instance=route_new, material_out_tracking=route.material_out.tracking)
            # route_new.material_out = material_out
            # if ind == 0:
            #     route_new.material_in = matin
            # elif route.material_in.process and route.material_in.process.id in genM:
            #     route_new.material_in = genM[route.material_in.process.id]
            # genM[process.id] = material_out
            route_new.routepack = obj_c
            route_new.create_by = user
            route_new.create_time = now
            route_new.save()
            for rm in RouteMat.objects.filter(route=route):
                rm_new = RouteMat()
                rm_new.route = route_new
                rm_new.material = rm.material
                rm_new.save()
        return Response({"id": route_new.id})
    
    @transaction.atomic
    @action(methods=['post'], detail=True, perms_map={'post': 'routepack.update'}, serializer_class=Serializer)
    def toggle_state(self, request, *args, **kwargs):
        """变更工艺路线状态

        变更工艺路线状态
        """
        ins:RoutePack = self.get_object()
        if  ins.state == RoutePack.RP_S_CONFIRM:
            ins.state = RoutePack.RP_S_CREATE
        elif ins.state == RoutePack.RP_S_CREATE:
            if ins.ticket is not None:
                ins.get_gjson(need_update=True)
                ins.state = RoutePack.RP_S_CONFIRM
            else:
                raise ParseError("该路线未提交审核")
        else:
            raise ParseError("该状态下不可变更")
        ins.save()
        return Response()

    @action(methods=['get'], detail=True, perms_map={'get': '*'})
    def dag(self, request, *args, **kwargs):
        """获取总图

        获取总图
        """
        return Response(Route.get_dag(rqs=Route.objects.filter(routepack=self.get_object())))
    
    @action(methods=['get'], detail=True, perms_map={'get': '*'})
    def dags(self, request, *args, **kwargs):
        """获取所有子图

        获取所有子图
        """
        ins = self.get_object()
        return Response(ins.get_dags())
    
    @action(methods=['get'], detail=True, perms_map={'get': '*'})
    def final_materials(self, request, *args, **kwargs):
        """获取最终产品

        获取最终产品"""
        ins:RoutePack = self.get_object()
        matIds = ins.get_final_material_ids()
        qs = Material.objects.filter(id__in=matIds)
        res = [{"id": x.id, "name": str(x)} for x in qs]
        return Response(res)

    @action(methods=['get'], detail=True, perms_map={'get': '*'})
    def validate(self, request, *args, **kwargs):
        """校验工艺图是否合法(不存盘)

        供拖拽画布实时校验DAG结构,返回 {valid, error},不修改gjson
        """
        ins:RoutePack = self.get_object()
        try:
            ins.validate_and_return_gjson()
            return Response({"valid": True, "error": ""})
        except ParseError as e:
            detail = e.detail
            if isinstance(detail, (list, tuple)):
                detail = detail[0] if detail else ""
            return Response({"valid": False, "error": str(detail)})
class RouteViewSet(CustomModelViewSet):
    queryset = Route.objects.all()
    serializer_class = RouteSerializer
    filterset_class = RouteFilter
    ordering = ['sort', 'process__sort', 'create_time']
    ordering_fields = ['sort', 'process__sort', 'create_time', 'update_time']
    select_related_fields = ['material',
                             'process', 'material_in', 'material_out', 'mgroup', 'routepack']

    def perform_update(self, serializer):
        ins:Route = serializer.instance
        if ins.from_route is not None:
            raise ParseError('该工艺步骤引用其他步骤, 无法编辑')
        old_m_in, old_m_out, process = ins.material_in, ins.material_out, ins.process
        routepack = ins.routepack
        if routepack and routepack.state != RoutePack.RP_S_CREATE:
            raise ParseError('该工艺路线非创建中不可编辑')
        ins_n:Route = serializer.save()
        if Route.objects.filter(from_route__id=ins.id).exists() and (ins_n.material_in != old_m_in or ins_n.material_out != old_m_out or ins_n.process != process):
            raise ParseError("该工艺步骤被其他步骤引用, 无法修改关键信息")

    def perform_destroy(self, instance:Route):
        if Route.objects.filter(from_route=instance).exists():
            raise ParseError('该工艺步骤被其他步骤引用，无法删除')
        return super().perform_destroy(instance)

    @action(methods=['post'], detail=False, perms_map={'post': '*'}, serializer_class=Serializer)
    def dag(self, request, *args, **kwargs):
        """获取总图

        获取总图
        """
        materialId = self.request.data.get('product', None)
        if materialId is None:
            raise ParseError('缺少参数product')
        return Response(Route.get_dag(rqs=Route.objects.filter(material__id=materialId)))

class SruleViewSet(CustomModelViewSet):
    """
    list: 排班规则
    """
    queryset = Srule.objects.all()
    serializer_class = SruleSerializer
    select_related_fields = ['belong_dept']
    filterset_fields = ['belong_dept']
    search_fields = ['belong_dept__name']
    
    def add_info_for_list(self, data):
        rule_list = [x['rule'] for x in data]
        teamIds = []
        for rule in rule_list:
            teamIds.extend(rule)
        teams = Team.objects.filter(id__in=teamIds).values_list('id', 'name')
        teams_dict = dict(teams)
        for item in data:
            rule = item["rule"]
            item["rule_display"] = "->".join([teams_dict.get(x, '未知') for x in rule])
        return data

class RouteMatViewSet(CustomListModelMixin, BulkCreateModelMixin, BulkDestroyModelMixin, CustomGenericViewSet):
    perms_map = {"get": "*", "post": "route.update", "delete": "route.update"}
    queryset = RouteMat.objects.all()
    serializer_class = RouteMatSerializer
    filterset_fields = ['route', 'material']