package cn.fateverse.workflow.process;

import cn.fateverse.common.code.engine.JavaScriptEngine;
import cn.fateverse.common.code.model.EngineResult;
import cn.fateverse.workflow.constant.ProcessConstant;
import cn.fateverse.workflow.entity.bpmn.*;
import cn.hutool.core.util.StrUtil;
import cn.fateverse.common.core.exception.CustomException;
import cn.fateverse.common.email.entity.SendEmailInfo;
import cn.fateverse.common.email.service.EmailService;
import cn.fateverse.workflow.entity.HistoricalOperation;
import cn.fateverse.workflow.enums.OperationEnums;
import cn.fateverse.workflow.enums.OperationStateEnums;
import cn.fateverse.workflow.mapper.HistoricalOperationMapper;
import cn.fateverse.workflow.mapper.UserInstanceMapper;
import com.alibaba.fastjson2.JSON;
import jdk.nashorn.api.scripting.ScriptObjectMirror;
import lombok.extern.slf4j.Slf4j;
import org.flowable.engine.RuntimeService;
import org.flowable.engine.delegate.DelegateExecution;
import org.jetbrains.annotations.NotNull;
import org.springframework.core.ParameterizedTypeReference;
import org.springframework.http.*;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Component;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.TransactionDefinition;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.support.DefaultTransactionDefinition;
import org.springframework.web.client.RestTemplate;

import java.util.*;
import java.util.function.Function;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;

/**
 * @author Clay
 * @date 2023-03-22
 */
@Slf4j
@Component
public class TriggerService {

    private final RestTemplate restTemplate;

    private final EmailService emailService;

    private final RuntimeService runtimeService;

    private final UserInstanceMapper userInstanceMapper;

    private final HistoricalOperationMapper operationMapper;

    private final PlatformTransactionManager txManager;

    public TriggerService(EmailService emailService,
                          RuntimeService runtimeService,
                          UserInstanceMapper userInstanceMapper,
                          HistoricalOperationMapper operationMapper,
                          PlatformTransactionManager txManager) {
        this.emailService = emailService;
        this.runtimeService = runtimeService;
        this.userInstanceMapper = userInstanceMapper;
        this.operationMapper = operationMapper;
        this.txManager = txManager;
        this.restTemplate = new RestTemplate();
    }

    /**
     * 发送http请求(异步)
     *
     * @param processNode 节点信息
     * @param operation   流程实例id
     */
    @Async
    public void asyncSendHttpRequest(ProcessNode processNode, HistoricalOperation operation, DelegateExecution execution) {
        log.info("开始异步发送请求");
        DefaultTransactionDefinition def = new DefaultTransactionDefinition();
        def.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRES_NEW);
        TransactionStatus status = txManager.getTransaction(def);
        try {
            sendHttpRequest(processNode, operation, execution);
            txManager.commit(status);
        } catch (Exception e) {
            e.printStackTrace();
            txManager.rollback(status);
        }
    }

    /**
     * 发送http请求
     *
     * @param processNode 节点信息
     * @param operation   流程实例id
     * @param execution   执行器中的对象
     */
    public void sendHttpRequest(ProcessNode processNode, HistoricalOperation operation, DelegateExecution execution) {
        log.info("发送请求");
        //获取到节点中http相关的数据信息
        HttpInfo http = processNode.getProps().getHttp();
        //选取当前http的请求类型
        HttpMethod httpMethod;
        switch (http.getMethod()) {
            case "GET":
                httpMethod = HttpMethod.GET;
                break;
            case "POST":
                httpMethod = HttpMethod.POST;
                break;
            case "PUT":
                httpMethod = HttpMethod.PUT;
                break;
            case "DELETE":
                httpMethod = HttpMethod.DELETE;
                break;
            default:
                throw new CustomException("请求类型错误!");
        }
        HttpHeaders headers = getHttpHeaders(http);
        HttpEntity<String> entity = new HttpEntity<>(headers);
        //制作响应头
        ParameterizedTypeReference<HashMap<String, Object>> responseType = new ParameterizedTypeReference<HashMap<String, Object>>() {
        };

        //发起请求
        ResponseEntity<HashMap<String, Object>> response;
        try {
            switch (httpMethod) {
                case GET:
                case DELETE:
                    String url = getGetAndDeleteParams(http, execution.getVariables());
                    response = restTemplate.exchange(url, httpMethod, entity, responseType);
                    break;
                case POST:
                case PUT:
                    Map<String, Object> params = getPostAndPutParams(http, execution.getVariables());
                    response = restTemplate.exchange(http.getUrl(), httpMethod, entity, responseType, params);
                    break;
                default:
                    throw new CustomException("请求类型不支持");
            }
        } catch (Exception e) {
            if (e instanceof CustomException) {
                CustomException custom = (CustomException) e;
                String message = custom.getMessage();
                operation.setState(OperationStateEnums.REFUSE);
                changeOperation(operation, message);
                if (http.getHandlerByScript()) {
                    rejectionProcess(execution, "http请求异常,流程自动终止!");
                }
            }
            return;
        }
        if (!http.getHandlerByScript()) {
            changeOperation(operation, true);
            return;
        }
        //获取到请求的返回结果
        Map<String, Object> result = response.getBody();
        EngineResult engineResult;
        try {
            //判断请求是否有效
            if (response.getStatusCode() == HttpStatus.OK) {
                engineResult = JavaScriptEngine.execute(http.getSuccess(), "handlerSuccess", false, result);
                operation.setState(OperationStateEnums.SUCCESS);
            } else {
                engineResult = JavaScriptEngine.execute(http.getFail(), "handlerFail", false, result);
                operation.setState(OperationStateEnums.FAILURE);
            }
        } catch (Exception e) {
            String message = e.getMessage();
            operation.setState(OperationStateEnums.REFUSE);
            changeOperation(operation, message);
            if (http.getHandlerByScript()) {
                rejectionProcess(execution, "http请求异常,流程自动终止!");
            }
            return;
        }
        ScriptObjectMirror jsResult = (ScriptObjectMirror) engineResult.getResult();
        //获取到自定义脚本的状态
        boolean state = (Boolean) jsResult.get("state");
        //获取到js脚本中的内容
        changeOperation(operation, jsResult.get("msg").toString());
        //如果返回结果为false,需要停止流程的执行
        if (!state) {
            rejectionProcess(execution, "http请求发送失败,驳回流程");
        }
    }

    /**
     * 获取post和pur的参数
     *
     * @param http      请求对象信息
     * @param variables 流程参数
     * @return 参数信息
     */
    private Map<String, Object> getPostAndPutParams(HttpInfo http, Map<String, Object> variables) {
        List<HttpParam> params = http.getParams();
        if (null == params) {
            return new HashMap<>();
        }
        Map<String, Object> resultParams = new HashMap<>();
        for (HttpParam param : params) {
            if (!StrUtil.isEmpty(param.getName()) && !StrUtil.isEmpty(param.getValue())) {
                Object value;
                if (param.getIsField()) {
                    value = variables.get(param.getValue());
                } else {
                    value = param.getValue();
                }
                resultParams.put(param.getName(), value);
            }
        }
        return resultParams;
    }


    /**
     * 获取到动态参数名称
     *
     * @param url 请求路由
     * @return 返回参数名称
     */
    public Set<String> getDynamicParamNames(String url) {
        Set<String> paramNames = new HashSet<>();
        String regex = "\\{(\\w+)}";
        Pattern pattern = Pattern.compile(regex);
        Matcher matcher = pattern.matcher(url);
        while (matcher.find()) {
            String paramName = matcher.group(1);
            paramNames.add(paramName);
        }
        return paramNames;
    }

    /**
     * 获取get或者delete请求的url
     *
     * @param http      http信息
     * @param variables 流程参数
     * @return url
     */
    private String getGetAndDeleteParams(HttpInfo http, Map<String, Object> variables) {
        StringBuffer url = new StringBuffer();
        Map<String, HttpParam> httpParamMap = null;
        if (null != http.getParams()) {
            httpParamMap = http.getParams().stream().filter(param -> !StrUtil.isEmpty(param.getName()) && !StrUtil.isEmpty(param.getValue())).collect(Collectors.toMap(HttpParam::getName, Function.identity()));
        }
        Set<String> dynamicParamSet = getDynamicParamNames(http.getUrl());
        if (!dynamicParamSet.isEmpty()) {
            if (null == httpParamMap || httpParamMap.isEmpty()) {
                throw new CustomException("url含有动态参数,但是参数列表为空!");
            }
            Map<String, String> replaceParams = new HashMap<>();
            for (String param : dynamicParamSet) {
                HttpParam httpParam = httpParamMap.get(param);
                if (null == httpParam) {
                    throw new CustomException(param + "参数未设置");
                }
                Object value;
                if (httpParam.getIsField()) {
                    value = variables.get(httpParam.getValue());
                } else {
                    value = httpParam.getValue();
                }
                replaceParams.put(param, value.toString());
            }
            url = replaceDynamicParams(http.getUrl(), replaceParams);
        }
        if (http.getMethod().equals("DELETE")) {
            return url.toString();
        }
        if (null == httpParamMap || httpParamMap.isEmpty()) {
            return http.getUrl();
        }
        List<String> getMethodsParamsList = new ArrayList<>();
        for (String paramName : httpParamMap.keySet()) {
            if (!dynamicParamSet.contains(paramName)) {
                String paramStr = paramName + "=" + httpParamMap.get(paramName).getValue();
                getMethodsParamsList.add(paramStr);
            }
        }
        if (!getMethodsParamsList.isEmpty()) {
            url.append("?");
            for (int i = 0; i < getMethodsParamsList.size(); i++) {
                if (i != getMethodsParamsList.size() - 1) {
                    url.append(getMethodsParamsList.get(i)).append("&");
                } else {
                    url.append(getMethodsParamsList.get(i));
                }

            }
        }
        return url.toString();
    }

    /**
     * 替换
     *
     * @param url           请求路
     * @param dynamicParams 动态参数信息
     * @return 替换好的url
     */
    private StringBuffer replaceDynamicParams(String url, Map<String, String> dynamicParams) {
        String regex = "\\{(\\w+)}";
        Pattern pattern = Pattern.compile(regex);
        Matcher matcher = pattern.matcher(url);
        StringBuffer newUrl = new StringBuffer();
        while (matcher.find()) {
            String paramName = matcher.group(1);
            String paramValue = dynamicParams.get(paramName);
            if (paramValue != null) {
                matcher.appendReplacement(newUrl, paramValue);
            } else {
                matcher.appendReplacement(newUrl, "0");
            }
        }
        matcher.appendTail(newUrl);
        return newUrl;
    }


    @NotNull
    private HttpHeaders getHttpHeaders(HttpInfo http) {
        //选择 Content-Type
        HttpHeaders headers = new HttpHeaders();
        if (http.getContentType().equals("FORM")) {
            headers.set("Content-Type", "multipart/form-data");
        } else {
            headers.set("Content-Type", "application/json");
        }
        //组装header
        List<HttpParam> headerParams = http.getHeaders();
        if (null != headerParams && !headerParams.isEmpty()) {
            for (HttpParam param : headerParams) {
                if (StrUtil.isEmpty(param.getName()) || StrUtil.isEmpty(param.getValue())) {
                    continue;
                }
                headers.set(param.getName(), param.getValue());
            }
        }
        return headers;
    }

    /**
     * 改变操作历史
     *
     * @param operation 操作历史
     * @param msg       消息
     */
    private void changeOperation(HistoricalOperation operation, String msg) {
        CommentInfo comment = new CommentInfo();
        comment.setContext(msg);
        operation.setMessage(JSON.toJSONString(comment));
        //对当前的操作进行更新
        int resultNum = operationMapper.update(operation);
        if (resultNum == 0) {
            operationMapper.insert(operation);
        }
    }

    private void rejectionProcess(DelegateExecution execution, String reason) {
        //对流程进行结束的操作
        Date date = new Date();
        changVariables(execution, ProcessConstant.BUSINESS_STATUS_REJECT);
        HistoricalOperation failOperation = HistoricalOperation.builder()
                .taskId(HistoricalOperation.makeTaskId())
                .processInstanceId(execution.getProcessInstanceId())
                .operation(OperationEnums.COMMENT)
                .state(OperationStateEnums.FAILURE)
                .startTime(date.getTime())
                .finishTime(date)
                .build();
        failOperation.setNodeId(HistoricalOperation.makeTaskId());
        CommentInfo failComment = new CommentInfo();
        failComment.setContext(reason);
        failOperation.setMessage(JSON.toJSONString(failComment));
        //添加结束操作的操作记录
        operationMapper.insert(failOperation);
        //停止当前流程
        runtimeService.deleteProcessInstance(execution.getProcessInstanceId(), "http请求发送失败,驳回流程");
        userInstanceMapper.updateState(execution.getProcessInstanceId(), ProcessConstant.BUSINESS_STATUS_REJECT);
    }


    /**
     * 改变运行装填
     *
     * @param execution 执行器任务信息
     * @param state     需要修改的状态
     */
    private void changVariables(DelegateExecution execution, String state) {
        //获取流程数据,并设置为拒绝状态
        Map<String, Object> variables = execution.getTransientVariables();
        variables.put(ProcessConstant.PROCESS_STATUS, state);
        //将表单数据设置到流程中
        runtimeService.setVariables(execution.getProcessInstanceId(), variables);
    }

    /**
     * 发送邮件(异步）
     *
     * @param processNode 节点信息
     * @param operation   流程实例id
     */
    @Async
    public void asyncSendEmail(ProcessNode processNode, HistoricalOperation operation) {
        log.info("开始异步发送邮件");
        sendEmail(processNode, operation);
    }

    /**
     * 发送邮件
     *
     * @param processNode 节点信息
     * @param operation   流程实例id
     */
    public void sendEmail(ProcessNode processNode, HistoricalOperation operation) {
        log.info("发送邮件");
        EmailInfo email = processNode.getProps().getEmail();
        SendEmailInfo send = new SendEmailInfo(email.getSubject(), email.getTo(), email.getCc(), email.getContent());
        boolean sendMailState = false;
        try {
            sendMailState = emailService.sendMail(send);
        } catch (Exception e) {
            log.error("邮件发送异常", e);
        }
        changeOperation(operation, sendMailState);
    }

    /**
     * 改变操作历史状态
     *
     * @param oldOperation 流程实例id
     * @param sendState    发送状态
     */
    private void changeOperation(HistoricalOperation oldOperation, boolean sendState) {
        HistoricalOperation operation = operationMapper.selectByTaskId(oldOperation.getTaskId());
        if (null == operation) {
            operation = oldOperation;
        }
        CommentInfo comment = new CommentInfo();
        if (sendState) {
            comment.setContext(oldOperation.getOperation().getDesc() + "发送成功");
            operation.setState(OperationStateEnums.SUCCESS);
        } else {
            comment.setContext(oldOperation.getOperation().getDesc() + "发送失败");
            operation.setState(OperationStateEnums.FAILURE);
        }
        log.info(comment.getContext());
        operation.setMessage(JSON.toJSONString(comment));
        int resultNum = operationMapper.update(operation);
        if (resultNum == 0) {
            operationMapper.insert(oldOperation);
        }
    }


}
