import importlib
from threading import Thread
from apps.utils.sms import send_sms
from apps.wf.serializers import TicketSimpleSerializer
from apps.system.models import Dept, User
from apps.wf.models import CustomField, State, Ticket, TicketFlow, Transition, Workflow
from rest_framework.exceptions import APIException, PermissionDenied, ValidationError
from django.utils import timezone
from datetime import timedelta, datetime
import random
from apps.utils.queryset import get_parent_queryset
from apps.wf.tasks import run_task
from rest_framework.exceptions import ParseError
import time

class WfService(object):
    @staticmethod
    def get_worlflow_states(workflow: Workflow):
        """
        获取工作流状态列表
        """
        return State.objects.filter(workflow=workflow, is_deleted=False).order_by('sort')

    @staticmethod
    def get_workflow_transitions(workflow: Workflow):
        """
        获取工作流流转列表
        """
        return Transition.objects.filter(workflow=workflow, is_deleted=False)

    @staticmethod
    def get_workflow_start_state(workflow: Workflow):
        """
        获取工作流初始状态
        """
        try:
            wf_state_obj = State.objects.get(workflow=workflow, type=State.STATE_TYPE_START, is_deleted=False)
            return wf_state_obj
        except Exception:
            raise APIException('工作流状态配置错误')

    @staticmethod
    def get_workflow_end_state(workflow: Workflow):
        """
        获取工作流结束状态
        """
        try:
            wf_state_obj = State.objects.get(workflow=workflow, type=State.STATE_TYPE_END, is_deleted=False)
            return wf_state_obj
        except Exception:
            raise APIException('工作流状态配置错误')

    @staticmethod
    def get_workflow_custom_fields(workflow: Workflow):
        """
        获取工单字段
        """
        return CustomField.objects.filter(is_deleted=False, workflow=workflow).order_by('sort')

    @staticmethod
    def get_workflow_custom_fields_list(workflow: Workflow):
        """
        获取工单字段key List
        """
        return list(CustomField.objects.filter(is_deleted=False,
                    workflow=workflow).order_by('sort').values_list('field_key', flat=True))

    @classmethod
    def get_ticket_transitions(cls, ticket: Ticket):
        """
        获取工单当前状态下可用的流转条件
        """
        return cls.get_state_transitions(ticket.state)

    @classmethod
    def get_state_transitions(cls, state: State):
        """
        获取状态可执行的操作
        """
        return Transition.objects.filter(is_deleted=False, source_state=state).all().order_by("-attribute_type", "-id")

    @classmethod
    def get_ticket_steps(cls, ticket: Ticket):
        steps = cls.get_worlflow_states(ticket.workflow)
        nsteps_list = []
        for i in steps:
            if ticket.state == i or (not i.is_hidden):
                nsteps_list.append(i)
        return nsteps_list

    @classmethod
    def get_transition_by_args(cls, kwargs: dict):
        """
        查询并获取流转
        """
        kwargs['is_deleted'] = False
        return Transition.objects.filter(**kwargs).all()

    @classmethod
    def get_ticket_sn(cls, workflow: Workflow, now=None):
        """
        生成工单流水号
        """
        if now is None:
            now = datetime.now()
        today = str(now)[:10]+' 00:00:00'
        ticket_day_count_new = Ticket.objects.get_queryset(all=True).filter(
            create_time__gte=today, create_time__lte=now, workflow=workflow).count()
        return '%s_%04d%02d%02d%04d' % (workflow.sn_prefix, now.year, now.month, now.day, ticket_day_count_new)

    @classmethod
    def get_next_state_by_transition_and_ticket_info(cls, ticket: Ticket,
                                                     transition: Transition, new_ticket_data: dict = {}) -> object:
        """
        获取下个节点状态
        """
        destination_state = transition.destination_state
        ticket_all_value = cls.get_ticket_all_field_value(ticket)
        ticket_all_value.update(**new_ticket_data)
        for key, value in ticket_all_value.items():
            if isinstance(ticket_all_value[key], str):
                ticket_all_value[key] = "'" + ticket_all_value[key] + "'"
        if transition.condition_expression:
            for i in transition.condition_expression:
                expression = i['expression'].format(**ticket_all_value)
                import datetime
                import time  # 用于支持条件表达式中对时间的操作
                if eval(expression, {'__builtins__': None}, {'datetime': datetime, 'time': time}):
                    destination_state = State.objects.get(pk=i['target_state'])
                    return destination_state
        return destination_state

    @classmethod
    def get_ticket_state_participant_info(cls, state: State,
                                          ticket: Ticket, new_ticket_data: dict = {}, handler: User = None):
        """
        获取工单目标状态实际的处理人, 处理人类型
        """
        if state.type == State.STATE_TYPE_START:
            """
            回到初始状态
            """
            return dict(destination_participant_type=State.PARTICIPANT_TYPE_PERSONAL,
                        destination_participant=ticket.create_by.id,
                        multi_all_person={})
        elif state.type == State.STATE_TYPE_END:
            """
            到达结束状态
            """
            return dict(destination_participant_type=0,
                        destination_participant=0,
                        multi_all_person={})
        multi_all_person_dict = {}
        destination_participant_type, destination_participant = state.participant_type, state.participant
        if destination_participant_type == State.PARTICIPANT_TYPE_FIELD:
            if destination_participant in new_ticket_data:
                destination_participant = new_ticket_data.get(destination_participant, 0)
            elif destination_participant in ticket.ticket_data:
                destination_participant = ticket.ticket_data.get(destination_participant, 0)
            elif hasattr(ticket, destination_participant):
                destination_participant = getattr(ticket, destination_participant).id
            else:
                destination_participant = 0

        elif destination_participant_type == State.PARTICIPANT_TYPE_FORMCODE:  # 代码获取
            parts = destination_participant.split('.')
            try:
                m = importlib.import_module('.'.join(parts[:-1]))
            except ModuleNotFoundError:
                m = getattr(importlib.import_module('.'.join(parts[:-2])), parts[-2])
            f = getattr(m, parts[-1])
            destination_participant = f(state=state, ticket=ticket, new_ticket_data=new_ticket_data, handler=handler)

        elif destination_participant_type == State.PARTICIPANT_TYPE_DEPT:  # 部门
            destination_participant = list(User.objects.filter(
                depts__in=destination_participant).values_list('id', flat=True))

        elif destination_participant_type == State.PARTICIPANT_TYPE_POST:  # 岗位
            user_queryset = User.objects.filter(posts__in=destination_participant)
            # 如果选择了岗位, 可能需要走过滤策略
            if state.filter_dept not in [0, '0', None]:
                # if not new_ticket_data.get(state.filter_dept, None):
                #     raise ParseError('部门过滤字段错误')
                if '.' not in state.filter_dept:
                    dpts = Dept.objects.filter(id=new_ticket_data[state.filter_dept])
                else:
                    dpt_attrs = state.filter_dept.split('.')  # 通过反向查询得到可能有多层
                    expr = ticket
                    for i in dpt_attrs:
                        try:
                            expr = getattr(expr, i)
                        except AttributeError as e:
                            if "'RelatedManager' object has no attribute" in str(e):
                                expr = getattr(expr.first(), i)
                            else:
                                raise
                    if expr is None:
                        raise ParseError('未找到对应部门')
                    dpts = Dept.objects.filter(id=expr.id)
                user_queryset = user_queryset.filter(depts__in=dpts)
            # if state.filter_policy == 1:
            #     depts = get_parent_queryset(ticket.belong_dept)
            #     user_queryset = user_queryset.filter(depts__in=depts)
            # elif state.filter_policy == 2:
            #     depts = get_parent_queryset(ticket.create_by.belong_dept)
            #     user_queryset = user_queryset.filter(depts__in=depts)
            # elif state.filter_policy == 3:
            #     depts = get_parent_queryset(handler.belong_dept)
            #     user_queryset = user_queryset.filter(depts__in=depts)
            destination_participant = list(user_queryset.values_list('id', flat=True))
        elif destination_participant_type == State.PARTICIPANT_TYPE_ROLE:  # 角色
            user_queryset = User.objects.filter(roles__in=destination_participant, is_active=True, is_deleted=False)
            # 如果选择了角色, 需要走过滤策略
            if state.filter_dept not in [0, '0', None]:
                # if not new_ticket_data.get(state.filter_dept, None):
                #     raise ParseError('部门过滤字段错误')
                if '.' not in state.filter_dept:
                    dpts = Dept.objects.filter(id=new_ticket_data[state.filter_dept])
                else:
                    dpt_attrs = state.filter_dept.split('.')
                    expr = ticket
                    for i in dpt_attrs:
                        expr = getattr(expr, i)
                    dpts = Dept.objects.filter(id=expr.id)
                user_queryset = user_queryset.filter(depts__in=dpts)
            # if state.filter_policy == 1:
            #     depts = get_parent_queryset(ticket.belong_dept)
            #     user_queryset = user_queryset.filter(depts__in=depts)
            # elif state.filter_policy == 2:
            #     depts = get_parent_queryset(ticket.create_by.belong_dept)
            #     user_queryset = user_queryset.filter(depts__in=depts)
            # elif state.filter_policy == 3:
            #     depts = get_parent_queryset(handler.belong_dept)
            #     user_queryset = user_queryset.filter(depts__in=depts)
            destination_participant = list(user_queryset.values_list('id', flat=True))
        if destination_participant_type != 0 and (not destination_participant):
            raise ParseError('下一步未找到处理人,工单无法继续')
        if type(destination_participant) == list:
            destination_participant_type = State.PARTICIPANT_TYPE_MULTI
            destination_participant = list(set(destination_participant))
            if len(destination_participant) == 1:  # 如果只有一个人
                destination_participant_type = State.PARTICIPANT_TYPE_PERSONAL
                destination_participant = destination_participant[0]
        elif destination_participant_type == State.PARTICIPANT_TYPE_ROBOT:
            pass
        else:
            destination_participant_type = State.PARTICIPANT_TYPE_PERSONAL
        if destination_participant_type == State.PARTICIPANT_TYPE_MULTI:
            if state.distribute_type == State.STATE_DISTRIBUTE_TYPE_RANDOM:
                destination_participant = random.choice(destination_participant)
            elif state.distribute_type == State.STATE_DISTRIBUTE_TYPE_ALL:
                for i in destination_participant:
                    multi_all_person_dict[i] = {}

        return dict(destination_participant_type=destination_participant_type,
                    destination_participant=destination_participant,
                    multi_all_person=multi_all_person_dict)

    @classmethod
    def ticket_handle_permission_check(cls, ticket: Ticket, user: User) -> dict:
        if ticket.in_add_node:
            return dict(permission=False, msg="工单当前处于加签中,请加签完成后操作", need_accept=False)
        transitions = cls.get_state_transitions(ticket.state)
        if not transitions:
            return dict(permission=True, msg="工单当前状态无需操作")
        participant = ticket.participant
        state = ticket.state
        if type(participant) == list:
            if user.id not in participant:
                    return dict(permission=False, msg="非当前处理人", need_accept=False)
            if len(participant) > 1 and state.distribute_type == State.STATE_DISTRIBUTE_TYPE_ACTIVE:
                return dict(permission=False, msg="需要先接单再处理", need_accept=True)
        else:
            if user.id != participant:
                return dict(permission=False, msg="非当前处理人", need_accept=False)
        return dict(permission=True, msg="", need_accept=False)

    @classmethod
    def check_dict_has_all_same_value(cls, dict_obj: object) -> tuple:
        """
        check whether all key are equal in a dict
        :param dict_obj:
        :return:
        """
        value_list = []
        for key, value in dict_obj.items():
            value_list.append(value)
        value_0 = value_list[0]
        for value in value_list:
            if value_0 != value:
                return False
        return True

    @classmethod
    def get_ticket_all_field_value(cls, ticket: Ticket) -> dict:
        """
        工单所有字段的值
        get ticket's all field value
        :param ticket:
        :return:
        """
        # 获取工单基础表中的字段中的字段信息
        field_info_dict = TicketSimpleSerializer(instance=ticket).data
        # 获取自定义字段的值
        custom_fields_queryset = cls.get_workflow_custom_fields(ticket.workflow)
        for i in custom_fields_queryset:
            field_info_dict[i.field_key] = ticket.ticket_data.get(i.field_key, None)
        return field_info_dict

    @classmethod
    def handle_ticket(cls, ticket: Ticket=None, transition: Transition=None, workflow: Workflow=None, new_ticket_data: dict = {}, oinfo: dict = {}, handler: User = None,
                      suggestion: str = '', by_timer: bool = False,
                      by_task: bool = False, by_hook: bool = False):
        just_created = False
        if ticket is None:
            # 创建工单逻辑
            if transition:
                if workflow and transition.workflow.id != workflow.id:
                    raise ParseError("当前流转不属于该工作流")
                workflow = transition.workflow
                
            start_state = WfService.get_workflow_start_state(workflow)
            save_ticket_data = {}
            if transition and transition.field_require_check:
                for key, value in start_state.state_fields.items():
                    if int(value) == State.STATE_FIELD_REQUIRED:
                        if key not in new_ticket_data and not new_ticket_data[key]:
                            raise ParseError('字段{}必填'.format(key))
                        save_ticket_data[key] = new_ticket_data[key]
                    elif int(value) == State.STATE_FIELD_OPTIONAL:
                        save_ticket_data[key] = new_ticket_data[key]
            else:
                save_ticket_data = new_ticket_data
            ticket = Ticket.objects.create(workflow=workflow,
                            state=start_state,
                            create_by=handler,
                            create_time=timezone.now(),
                            act_state=Ticket.TICKET_ACT_STATE_DRAFT,
                            belong_dept=handler.belong_dept,
                            ticket_data=save_ticket_data, participant_type=1, participant=handler.id)  # 先创建出来
           
            sn = WfService.get_ticket_sn(ticket.workflow)  # 流水号
            ticket.sn = sn
            ticket.save()
            if not transition:
                return ticket
            just_created = True  # 刚创建的工单不需要校验权限

        if transition and transition.source_state.type == State.STATE_TYPE_START:
            # 更新title和sn
            ticket_title = oinfo.get("title", "")
            title_template = ticket.workflow.title_template
            if title_template:
                all_ticket_data = {**oinfo, **new_ticket_data}
                try:
                    ticket_title = title_template.format(**all_ticket_data)
                except KeyError as e:
                    raise ParseError(f"工单标题模板中存在未定义的变量:{e}")
            ticket.title = ticket_title
            ticket.save(update_fields=["title"])

        source_state = ticket.state
        source_ticket_data = ticket.ticket_data

        if transition.source_state != source_state:
            raise ParseError('非该工单节点状态下的流转')
        
        # 提交时可能进行的操作
        if transition.on_submit_func:
            parts = transition.on_submit_func.split('.')
            try:
                m = importlib.import_module('.'.join(parts[:-1]))
            except ModuleNotFoundError:
                m = getattr(importlib.import_module('.'.join(parts[:-2])), parts[-2])
            f = getattr(m, parts[-1])
            f(ticket=ticket, transition=transition, new_ticket_data=new_ticket_data)

        # 校验处理权限
        if handler is not None and just_created is False:  # 有处理人意味着系统触发校验处理权限
            result = WfService.ticket_handle_permission_check(ticket, handler)
            if result.get('permission') is False:
                raise PermissionDenied(result.get('msg'))

        # 校验表单必填项目
        if transition.field_require_check or not just_created:
            for key, value in ticket.state.state_fields.items():
                if int(value) == State.STATE_FIELD_REQUIRED:
                    if key not in new_ticket_data or not new_ticket_data[key]:
                        raise ValidationError('字段{}必填'.format(key))

        destination_state = cls.get_next_state_by_transition_and_ticket_info(ticket, transition, new_ticket_data)
        multi_all_person = ticket.multi_all_person
        if multi_all_person:
            multi_all_person[handler.id] = dict(transition=transition.id)
            # 判断所有人处理结果是否一致
            if WfService.check_dict_has_all_same_value(multi_all_person):
                participant_info = WfService.get_ticket_state_participant_info(
                    destination_state, ticket, new_ticket_data)
                destination_participant_type = participant_info.get('destination_participant_type', 0)
                destination_participant = participant_info.get('destination_participant', 0)
                multi_all_person = {}
            else:
                # 处理人没有没有全部处理完成或者处理动作不一致
                destination_participant_type = ticket.participant_type
                destination_state = ticket.state  # 保持原状态
                destination_participant = []
                for key, value in multi_all_person.items():
                    if not value:
                        destination_participant.append(key)
        else:
            # 当前处理人类型非全部处理
            participant_info = WfService.get_ticket_state_participant_info(destination_state, ticket, new_ticket_data)
            destination_participant_type = participant_info.get('destination_participant_type', 0)
            destination_participant = participant_info.get('destination_participant', 0)
            multi_all_person = participant_info.get('multi_all_person', {})

        # 更新工单信息：基础字段及自定义字段， add_relation字段 需要下个处理人是部门、角色等的情况
        ticket.state = destination_state
        ticket.participant_type = destination_participant_type
        ticket.participant = destination_participant
        ticket.multi_all_person = multi_all_person
        if destination_state.type == State.STATE_TYPE_END:
            ticket.act_state = Ticket.TICKET_ACT_STATE_FINISH
        elif destination_state.type == State.STATE_TYPE_START:
            ticket.act_state = Ticket.TICKET_ACT_STATE_DRAFT
        else:
            ticket.act_state = Ticket.TICKET_ACT_STATE_ONGOING

        if transition.attribute_type == Transition.TRANSITION_ATTRIBUTE_TYPE_REFUSE:
            ticket.act_state = Ticket.TICKET_ACT_STATE_BACK

        # 只更新必填和可选的字段
        if not just_created and transition.field_require_check:
            for key, value in source_state.state_fields.items():
                if value in (State.STATE_FIELD_REQUIRED, State.STATE_FIELD_OPTIONAL):
                    if key in new_ticket_data:
                        source_ticket_data[key] = new_ticket_data[key]
            ticket.ticket_data = source_ticket_data
        ticket.save()

        # 更新工单流转记录
        if not by_task:
            TicketFlow.objects.create(ticket=ticket, state=source_state,
                                      ticket_data=WfService.get_ticket_all_field_value(ticket),
                                      suggestion=suggestion, participant_type=State.PARTICIPANT_TYPE_PERSONAL,
                                      participant=handler, transition=transition)

        if just_created:
            if source_state.participant_cc:
                TicketFlow.objects.create(ticket=ticket, state=source_state,
                                          participant_type=0, intervene_type=Transition.TRANSITION_INTERVENE_TYPE_CC,
                                          participant=None, participant_cc=source_state.participant_cc)

        # 目标状态需要抄送
        if destination_state.participant_cc:
            TicketFlow.objects.create(ticket=ticket, state=destination_state,
                                      participant_type=0, intervene_type=Transition.TRANSITION_INTERVENE_TYPE_CC,
                                      participant=None, participant_cc=destination_state.participant_cc)

        if destination_state.type == State.STATE_TYPE_END:
            TicketFlow.objects.create(ticket=ticket, state=destination_state,
                                      participant_type=0, intervene_type=0,
                                      participant=None)

        cls.task_ticket(ticket=ticket)

        # 自动跳过连续相同审批人：如果下一个节点的处理人与当前处理人相同，自动执行同意操作
        if (handler is not None
                and destination_state.type not in (State.STATE_TYPE_START, State.STATE_TYPE_END)
                and transition.attribute_type == Transition.TRANSITION_ATTRIBUTE_TYPE_ACCEPT
                and destination_participant_type == State.PARTICIPANT_TYPE_PERSONAL
                and str(destination_participant) == str(handler.id)):
            # 查找下一个状态的"同意"流转
            next_transition = Transition.objects.filter(
                is_deleted=False,
                source_state=destination_state,
                attribute_type=Transition.TRANSITION_ATTRIBUTE_TYPE_ACCEPT
            ).first()
            if next_transition:
                import logging
                logger = logging.getLogger(__name__)
                logger.info(f'工单{ticket.sn}: 连续节点审批人相同({handler.username})，自动跳过节点[{destination_state.name}]')
                ticket = cls.handle_ticket(
                    ticket=ticket,
                    transition=next_transition,
                    new_ticket_data=ticket.ticket_data,
                    handler=handler,
                    suggestion='（系统自动审批：与上一节点审批人相同）',
                )

        return ticket

    @classmethod
    def update_ticket_state(cls, ticket: Ticket, new_state: State, suggestion: str, handler: User, need_log:bool):
        participant_info = cls.get_ticket_state_participant_info(
            new_state, ticket, {})
        source_state = ticket.state
        ticket.state = new_state
        ticket.participant_type = participant_info.get('destination_participant_type', 0)
        ticket.participant = participant_info.get('destination_participant', 0)
        ticket.multi_all_person = {}
        ticket.save()
        if need_log:
            TicketFlow.objects.create(ticket=ticket, state=source_state,
                                    ticket_data=WfService.get_ticket_all_field_value(ticket),
                                    suggestion=suggestion, participant_type=State.PARTICIPANT_TYPE_PERSONAL,
                                    intervene_type=Transition.TRANSITION_INTERVENE_TYPE_ALTER_STATE,
                                    participant=handler)
        cls.task_ticket(ticket=ticket)

    @classmethod
    def task_ticket(cls, ticket: Ticket):
        """
        执行任务(自定义任务和通知)
        """
        state = ticket.state
        # 如果目标状态有func,由func执行额外操作(比如发送通知)
        if state.on_reach_func:
            parts = state.on_reach_func.split('.')
            try:
                m = importlib.import_module('.'.join(parts[:-1]))
            except ModuleNotFoundError:
                m = getattr(importlib.import_module('.'.join(parts[:-2])), parts[-2])
            f = getattr(m, parts[-1])
            f(ticket=ticket)  # 同步执行

        # wf默认只发送通知
        last_log = TicketFlow.objects.filter(ticket=ticket).order_by('-create_time').first()
        if (last_log.state != state or
            last_log.intervene_type == Transition.TRANSITION_INTERVENE_TYPE_DELIVER or
                ticket.in_add_node):
            # 如果状态变化或是转交加签的情况再发送通知
            cls.send_ticket_notice(ticketflow=last_log)

        # 如果目标状态是脚本则异步执行
        if state.participant_type == State.PARTICIPANT_TYPE_ROBOT:
            run_task.delay(ticket_id=ticket.id)
    
    @classmethod
    def send_ticket_notice(cls, ticketflow:TicketFlow):
        # 根据ticketflow发送通知
        Thread(target=send_ticket_notice_t, args=(ticketflow.id,), daemon=True).start()


    @classmethod
    def close_by_task(cls, ticket: Ticket, suggestion: str):
        # 定时任务触发的工单关闭
        end_state = WfService.get_workflow_end_state(ticket.workflow)
        ticket.state = end_state
        ticket.participant_type = 0
        ticket.participant = 0
        ticket.act_state = Ticket.TICKET_ACT_STATE_CLOSED
        ticket.save()
        # 更新流转记录
        TicketFlow.objects.create(ticket=ticket, state=ticket.state,
                                    ticket_data=WfService.get_ticket_all_field_value(ticket),
                                    suggestion=suggestion, participant_type=State.PARTICIPANT_TYPE_ROBOT,
                                    intervene_type=Transition.TRANSITION_INTERVENE_TYPE_CLOSE, transition=None)
    @classmethod
    def retreat(cls, ticket: Ticket, suggestion: str, handler: User, next_handler: User):
        """
        回退
        """
        start_state = WfService.get_workflow_start_state(ticket.workflow)
        ticket.state = start_state
        ticket.participant_type = State.PARTICIPANT_TYPE_PERSONAL
        ticket.participant = next_handler.id
        ticket.act_state = Ticket.TICKET_ACT_STATE_RETREAT
        ticket.save()
        # 更新流转记录
        TicketFlow.objects.create(ticket=ticket, state=ticket.state,
                                  ticket_data=WfService.get_ticket_all_field_value(ticket),
                                  suggestion=suggestion, participant_type=State.PARTICIPANT_TYPE_PERSONAL,
                                  intervene_type=Transition.TRANSITION_INTERVENE_TYPE_RETREAT,
                                  participant=handler, transition=None)
        cls.task_ticket(ticket=ticket)

def send_ticket_notice_t(ticketflowId: str):
    """
    发送通知
    """
    time.sleep(3)
    ticket = TicketFlow.objects.get(id=ticketflowId).ticket
    params = {'workflow': ticket.workflow.name, 'state': ticket.state.name}
    if ticket.participant_type == 1:
        # 发送短信通知
        pt = User.objects.filter(id=ticket.participant).first()
        if pt and pt.phone:
            send_sms(pt.phone, 1002, params)
    elif ticket.participant_type == 2:
        pts = User.objects.filter(id__in=ticket.participant)
        for i in pts:
            if i.phone:
                send_sms(i.phone, 1002, params)
