from apps.cm.models import LableMat, LabelTemplate
from rest_framework.decorators import action
from apps.cm.serializers import TidSerializer, LabelMatSerializer, LabelTemplateSerializer, Tid2Serializer, CoderSendSerializer, HanslaserSendSerializer
from apps.cm.coder import CoderClient, HanslaserClient
from apps.inm.models import MaterialBatch, MIOItem
from apps.wpm.models import WMaterial
from rest_framework.exceptions import ParseError, NotFound
from rest_framework.response import Response
from apps.utils.viewsets import CustomGenericViewSet, RetrieveModelMixin, CustomListModelMixin, CustomModelViewSet
from apps.cm.filters import LabelTemplateFilter
# Create your views here.

SPLIT_FIELD = "#"

class LableMatViewSet(CustomListModelMixin, RetrieveModelMixin, CustomGenericViewSet):
    perms_map = {"post": "*", "get": "*"}
    serializer_class = LabelMatSerializer
    queryset = LableMat.objects.all()
    select_related_fields = ["material", "material_origin", "supplier"]

    @action(methods=["post"], detail=False, serializer_class=TidSerializer)
    def get_from_mb(self, request, pk=None):
        """
        从仓库明细获取物料标签

        从仓库明细获取物料标签
        """
        tid = request.data.get("tid")
        label_template = request.data.get("label_template", None)
        label_template_name = request.data.get("label_template_name", None)
        extra_data = request.data.get("extra_data", {})
        try:
            mb = MaterialBatch.objects.get(id=tid)
        except MaterialBatch.DoesNotExist:
            raise NotFound("仓库明细不存在")
        obj, _ = LableMat.objects.get_or_create(state=mb.state, material=mb.material, batch=mb.batch, defect=mb.defect, defaults={"supplier": mb.supplier})
        rdata = LabelMatSerializer(obj).data
        rdata["code_label"] = f"mat{SPLIT_FIELD}{obj.id}"
        if label_template or label_template_name:
            tdata = {**rdata, **extra_data}
            commands = LabelTemplate.gen_commands(label_template, label_template_name, tdata)
            rdata["commands"] = commands
        return Response(rdata)

    @action(methods=["post"], detail=False, serializer_class=TidSerializer)
    def get_from_wm(self, request, pk=None):
        """
        从车间库存明细获取物料标签

        从车间库存明细获取物料标签
        """
        tid = request.data.get("tid")
        label_template = request.data.get("label_template", None)
        label_template_name = request.data.get("label_template_name", None)
        extra_data = request.data.get("extra_data", {})
        try:
            wm = WMaterial.objects.get(id=tid)
        except WMaterial.DoesNotExist:
            raise NotFound("车间库存不存在")
        obj, _ = LableMat.objects.get_or_create(state=wm.state, material=wm.material, batch=wm.batch, 
                                                notok_sign=wm.notok_sign, defect=wm.defect,
                                                material_origin=wm.material_origin)
        rdata = LabelMatSerializer(obj).data
        rdata["code_label"] = f"mat{SPLIT_FIELD}{obj.id}"
        if label_template or label_template_name:
            tdata = {**rdata, **extra_data}
            commands = LabelTemplate.gen_commands(label_template, label_template_name, tdata)
            rdata["commands"] = commands
        return Response(rdata)

    @action(methods=["post"], detail=False, serializer_class=TidSerializer)
    def get_from_mioitem(self, request, pk=None):
        """
        从出入库明细获取物料标签

        从出入库明细获取物料标签
        """
        tid = request.data.get("tid")
        label_template = request.data.get("label_template", None)
        label_template_name = request.data.get("label_template_name", None)
        extra_data = request.data.get("extra_data", {})
        try:
            mioitem: MIOItem = MIOItem.objects.get(id=tid)
        except MIOItem.DoesNotExist:
            raise NotFound("出入库明细不存在")
        obj, _ = LableMat.objects.get_or_create(state=10, material=mioitem.material, batch=mioitem.batch, defaults={"supplier": mioitem.mio.supplier})
        rdata = LabelMatSerializer(obj).data
        rdata["code_label"] = f"mat{SPLIT_FIELD}{obj.id}"
        if label_template or label_template_name:
            tdata = {**rdata, **extra_data}
            commands = LabelTemplate.gen_commands(label_template, label_template_name, tdata)
            rdata["commands"] = commands
        return Response(rdata)


class LabelTemplateViewSet(CustomModelViewSet):
    """
    list: 标签模板

    标签模板
    """
    queryset = LabelTemplate.objects.all()
    serializer_class = LabelTemplateSerializer
    filterset_class = LabelTemplateFilter

    @action(methods=["post"], detail=False, serializer_class=Tid2Serializer, perms_map={"post": "*"})
    def commands(self, request, *args, **kwargs):
        """
        获取标签指令

        获取标签指令
        """
        label_template = request.data.get("label_template", None)
        label_template_name = request.data.get("label_template_name", None)
        data = request.data.get("data", {})
        return Response({"commands": LabelTemplate.gen_commands(label_template, label_template_name, data)})

    @action(methods=["post"], detail=True, serializer_class=CoderSendSerializer, perms_map={"post": "*"})
    def send_to_coder(self, request, pk=None, *args, **kwargs):
        """
        下发指令到伟迪捷 1880 喷码机 (统一走 CQI + JDI 队列模式)

        body 传 tdata_list (列表, 至少一条), 每条对应一个产品。
        每次调用先 CQI 清空队列, 再按顺序 JDI 入队 N 条;
        喷码机每个外部触发(光电)消费一条, 喷一个产品。
        每条 tdata 走模板 commands, 解析为 <字段>=<内容> 合并成一帧 JDI;
        commands 中不含 '=' 的元素, 会绑定到默认 coder_field 上。
        """
        sr = CoderSendSerializer(data=request.data)
        sr.is_valid(raise_exception=True)
        vdata = sr.validated_data
        lt: LabelTemplate = self.get_object()

        ip = vdata.get("coder_ip")
        port = vdata.get("coder_port") or 3100
        field = vdata.get("coder_field") or "1"

        batched = []
        for tdata in vdata["tdata_list"]:
            commands = LabelTemplate.gen_commands(lt.id, None, tdata)
            if not commands:
                raise ParseError("模板未生成任何指令")
            fields = {}
            for item in commands:
                text = str(item)
                if "=" in text:
                    k, v = text.split("=", 1)
                    fields[k.strip()] = v
                else:
                    fields[field] = text
            batched.append(fields)

        client = CoderClient(ip=ip, port=port)
        coder_jobname = vdata.get("coder_jobname")
        client.clear_queue()
        if coder_jobname:
            client.select_job(coder_jobname)
        client.clear_queue()
        for fields in batched:
            client.push_queue(fields)
        return Response({"queued": len(batched), "fields": batched, "ip": ip, "port": port, "jobname": coder_jobname})

    @action(methods=["post"], detail=True, serializer_class=HanslaserSendSerializer, perms_map={"post": "*"})
    def send_to_hanslaser(self, request, pk=None, *args, **kwargs):
        """
        下发指令到大族激光(Hanslaser)老款打标机 (单件打标, 协议无队列能力)

        body 传 tdata_list, 可选 coder_jobname 先选打标文件。
        对 tdata_list 逐件执行 $Data_<逗号值> -> $MarkStart_, 每步等下位机回包;
        模板 commands 取每个元素作为位置值(含 '=' 时取等号右侧), 按顺序逗号拼接。
        传多条会同步逐件打完, 单件场景传 1 条即可。
        """
        sr = HanslaserSendSerializer(data=request.data)
        sr.is_valid(raise_exception=True)
        vdata = sr.validated_data
        lt: LabelTemplate = self.get_object()

        ip = vdata.get("coder_ip")
        port = vdata.get("coder_port")
        coder_jobname = vdata.get("coder_jobname")

        batched = []
        for tdata in vdata["tdata_list"]:
            commands = LabelTemplate.gen_commands(lt.id, None, tdata)
            if not commands:
                raise ParseError("模板未生成任何指令")
            values = []
            for item in commands:
                text = str(item)
                values.append(text.split("=", 1)[1] if "=" in text else text)
            batched.append(values)

        client = HanslaserClient(ip=ip, port=port)
        if coder_jobname:
            client.initialize(coder_jobname)
        marked = 0
        for values in batched:
            client.send_data(values)
            client.mark_start()
            marked += 1
        return Response({"marked": marked, "values": batched, "ip": ip, "port": port, "jobname": coder_jobname})
