from apps.utils.serializers import CustomModelSerializer
from apps.enm.models import Mpoint, MpointStat, EnStat, EnStat2, MpLogx, Xscript
from apps.utils.constants import EXCLUDE_FIELDS, EXCLUDE_FIELDS_BASE
from rest_framework import serializers
from apps.mtm.models import Mgroup
from rest_framework.exceptions import ParseError
from django.core.cache import cache
from django.db import transaction
from django_celery_beat.models import PeriodicTask
from django_celery_results.models import TaskResult
from apps.system.serializers import PTaskResultSerializer
import json
from apps.utils.tools import CodeAnalyzer


class MpointSerializer(CustomModelSerializer):
    mgroup = serializers.PrimaryKeyRelatedField(label="测点集", queryset=Mgroup.objects.all(), required=False, allow_null=True)
    mgroup_name = serializers.CharField(source="mgroup.name", read_only=True)
    belong_dept_name = serializers.CharField(source="belong_dept.name", read_only=True)
    ep_monitored_name = serializers.CharField(source="ep_monitored.name", read_only=True)
    ep_monitored_power_kw = serializers.CharField(source="ep_monitored.power_kw", read_only=True)
    ep_belong_name = serializers.CharField(source="ep_belong.name", read_only=True)
    material_name = serializers.CharField(source="material.name", read_only=True)
    formula = serializers.CharField(allow_blank=True, required=False)
    last_data = serializers.SerializerMethodField()
    gather_state = serializers.SerializerMethodField()

    class Meta:
        model = Mpoint
        fields = "__all__"
        read_only_fields = EXCLUDE_FIELDS + ["belong_dept", "cate"]

    def create(self, validated_data):
        code = validated_data["code"]
        if Mpoint.objects.get_queryset(all=True).filter(code=code).exists():
            raise ParseError("测点编号已存在")
        return super().create(validated_data)
    
    def get_last_data(self, obj):
        cache_mp = cache.get(Mpoint.cache_key(obj.code))
        if isinstance(cache_mp, dict):
            last_data = cache_mp.get('last_data', {})
        else:
            last_data = {}
        return last_data
    
    def get_gather_state(self, obj):
        cache_mp = cache.get(Mpoint.cache_key(obj.code))
        if isinstance(cache_mp, dict):
            gather_state = cache_mp.get('gather_state', -2)
        else:
            gather_state = -2
        return gather_state

    def validate(self, attrs):
        if "material" in attrs and attrs["material"]:
            attrs["cate"] = "material"
        if "mgroup" in attrs and attrs["mgroup"]:
            attrs["belong_dept"] = attrs["mgroup"].belong_dept
        formula = attrs.get("formula", "")
        if formula:
            pass
        return attrs


# class MpLogSerializer(CustomModelSerializer):
#     mpoint_name = serializers.CharField(source='mpoint.name', read_only=True)
#     class Meta:
#         model = MpLog
#         fields = '__all__'
#         read_only_fields = EXCLUDE_FIELDS + ['mpoint_name']


class MpLogxSerializer(CustomModelSerializer):
    """Serializer for EnvData model"""

    class Meta:
        model = MpLogx
        fields = "__all__"


class MpointStatSerializer(CustomModelSerializer):
    mpoint_name = serializers.CharField(source="mpoint.name", read_only=True)
    mpoint_nickname = serializers.CharField(source="mpoint.nickname", read_only=True)
    ep_monitored_name = serializers.CharField(source="mpoint.ep_monitored.name", read_only=True)
    ep_monitored_number = serializers.CharField(source="mpoint.ep_monitored.number", read_only=True)
    ep_monitored_power_kw = serializers.CharField(source="mpoint.ep_monitored.power_kw", read_only=True)
    ep_belong_name = serializers.CharField(source="mpoint.ep_belong.name", read_only=True)
    mgroup_name = serializers.CharField(source="mgroup.name", read_only=True)
    belong_dept_name = serializers.CharField(source="mgroup.belong_dept.name", read_only=True)
    sflog_start_time = serializers.DateTimeField(source="sflog.start_time", read_only=True)
    sflog_end_time = serializers.DateTimeField(source="sflog.end_time", read_only=True)

    class Meta:
        model = MpointStat
        fields = "__all__"
        read_only_fields = EXCLUDE_FIELDS + ["mpoint_name", "type", "year", "month", "day"]

    def check_required_keys(dictionary, keys):
        for key in keys:
            if key not in dictionary or not dictionary[key]:
                return False
        return True
    
    def create(self, validated_data):
        if MpointStat.objects.filter(mpoint=validated_data["mpoint"], sflog=validated_data["sflog"]).exists():
            raise ParseError("该数据已录入")
        return super().create(validated_data)

    def validate(self, attrs):
        mpoint = attrs["mpoint"]
        if 'mgroup' not in attrs:
            raise ParseError("请选择工段")
        if mpoint.material and mpoint.type is Mpoint.MT_MANUAL and "sflog" in attrs and attrs["sflog"]:
            attrs["type"] = "sflog"
            sflog = attrs["sflog"]
            attrs["year_s"], attrs["month_s"], attrs["day_s"] = sflog.get_ymd
            attrs["mgroup"] = sflog.mgroup
        else:
            raise ParseError("该数据不支持手工录入")
        # if 'sflog' in attrs and attrs['sflog']:

        # else:
        #     keys = ['hour', 'day_s', 'month_s', 'year_s']
        #     for ind, key in enumerate(keys):
        #         if key in attrs and attrs['key']:
        #             if not self.check_required_keys(attrs, keys[ind+1:]):
        #                 raise ParseError('缺少数据')
        #             attrs['type'] = key
        return super().validate(attrs)

    def to_representation(self, instance):
        ret = super().to_representation(instance)
        my_dic_keys = list(ret.keys())
        for key in my_dic_keys:
            ret_one_val = ret[key]
            if isinstance(ret_one_val, float):
                ret[key] = "{:.2f}".format(round(ret_one_val, 2))
        return ret

class EnStatUpdateSerializer(CustomModelSerializer):
    class Meta:
        model = EnStat
        fields = ["id", "note"]

class EnStatSerializer(CustomModelSerializer):
    mgroup_name = serializers.CharField(source="mgroup.name", read_only=True)
    team_name = serializers.CharField(source="team.name", read_only=True)
    belong_dept_name = serializers.CharField(source="mgroup.belong_dept.name", read_only=True)

    class Meta:
        model = EnStat
        fields = "__all__"

    def to_representation(self, instance):
        ret = super().to_representation(instance)
        if 'run_sec' in ret:
            ret['run_hour'] = ret['run_sec'] / 3600
        my_dic_keys = list(ret.keys())
        for key in my_dic_keys:
            ret_one_val = ret[key]
            if isinstance(ret_one_val, float):
                if key in ["en_consume_unit"]:
                    ret[key] = "{:.4f}".format(round(ret_one_val, 4))
                else:
                    ret[key] = "{:.2f}".format(round(ret_one_val, 2))
            qua_data = ret.get("qua_data", {})
            
            equip_elec_data = ret.get("equip_elec_data", {})
            if qua_data: 
                for item in qua_data:
                    if item.get("rate_pass" )is None:
                        ret[f'{item["material_name"]}_{item["testitem_name"].replace("-", "")}_rate_pass'] = 0
                    else:
                        value = item["rate_pass"]
                        rounded_value = round(value, 2) if value is not None else 0
                        formatted_value = "{:.2f}".format(rounded_value)
                        ret[f'{item["material_name"]}_{item["testitem_name"].replace("-", "")}_rate_pass'] = formatted_value
            if equip_elec_data:
                for item in equip_elec_data:
                    val = item.get("consume_unit", None)
                    if val:
                        val = "{:.2f}".format(round(val, 2))
                    ret[f'{item["equipment_name"]}_consume_unit'] = val
        # 根据material_name分组排序
        if 'qua_data' in ret:
            ret['qua_data'] = sorted(ret['qua_data'], key=lambda x: x['material_name'])
        return ret


class EnStat2Serializer(CustomModelSerializer):
    class Meta:
        model = EnStat2
        fields = "__all__"

    def to_representation(self, instance):
        ret = super().to_representation(instance)
        my_dic_keys = list(ret.keys())
        for key in my_dic_keys:
            ret_one_val = ret[key]
            if isinstance(ret_one_val, float):
                ret[key] = "{:.2f}".format(round(ret_one_val, 2))
        return ret


class ReCalSerializer(serializers.Serializer):
    start_time = serializers.DateTimeField(label="开始时间")
    end_time = serializers.DateTimeField(label="结束时间")
    mpoint_stat = serializers.BooleanField(label="从MpointStat开始计算", required=False, default=False)


class MpointStatCorrectSerializer(CustomModelSerializer):
    class Meta:
        model = MpointStat
        fields = ['val_correct', 'id']

class EnStatAnaSerializer(serializers.Serializer):
    start_date = serializers.DateField(label="开始日期")
    end_date = serializers.DateField(label="结束日期")


class XscriptSerializer(CustomModelSerializer):
    myschedule_name = serializers.CharField(source="myschedule.name", read_only=True)
    enabled = serializers.BooleanField(source='periodictask.enabled', read_only=True)
    last_run_at = serializers.DateTimeField(source='periodictask.last_run_at', read_only=True)
    total_run_count = serializers.IntegerField(source='periodictask.total_run_count', read_only=True)
    class Meta:
        model = Xscript
        fields = "__all__"
        read_only_fields = EXCLUDE_FIELDS_BASE + ['periodictask', 'change_data']
    
    def validate(self, attrs):
        code = attrs['code']
        analyzer = CodeAnalyzer()
        analyzer.analyze(code)
        if analyzer.errors:
            raise ParseError(f'脚本错误:{analyzer.errors[0]}')
        return super().validate(attrs)

    @transaction.atomic
    def create(self, validated_data):
        ins: Xscript = super().create(validated_data)
        pobj = PeriodicTask()
        pobj.name = f'{ins.name}_{str(ins.id)[-8:]}'
        pobj.task = 'apps.enm.tasks.insert_mplogx_from_xscript'
        sch = ins.myschedule
        if sch.crontab:
            pobj.crontab = sch.crontab
        elif sch.interval:
            pobj.interval = sch.interval
        else:
            raise ParseError('不支持的定时策略')
        pobj.kwargs = json.dumps({'xscript_id': ins.id})
        pobj.enabled = False
        pobj.save()
        ins.periodictask = pobj
        ins.save()
        return ins

    @transaction.atomic
    def update(self, instance, validated_data):
        ins = super().update(instance, validated_data)
        pobj = instance.periodictask
        pobj.name = f'{ins.name}_{str(ins.id)[-8:]}'
        sch = ins.myschedule
        if sch.crontab:
            pobj.crontab = sch.crontab
            pobj.interval = None
        elif sch.interval:
            pobj.interval = sch.interval
            pobj.crontab = None
        else:
            raise ParseError('不支持的定时策略')
        pobj.save()
        return ins
    

class XscriptDetailSerializer(XscriptSerializer):
    last_run_data = serializers.SerializerMethodField()

    def get_last_run_data(self, obj):
        tr = TaskResult.objects.filter(task_kwargs__contains=obj.id).order_by('-date_done').first()
        if tr:
            return PTaskResultSerializer(instance=tr).data
        return None