package cn.axzo.workflow.starter.feign.ext;

import cn.axzo.framework.rocketmq.EventProducer;
import cn.axzo.workflow.common.enums.RpcInvokeModeEnum;
import cn.axzo.workflow.common.model.response.mq.WorkflowEngineStarterRpcInvokeDTO;
import cn.axzo.workflow.common.util.ThreadUtil;
import cn.axzo.workflow.starter.WorkflowEngineStarterProperties;
import cn.axzo.workflow.starter.common.exception.WorkflowEngineStarterException;
import cn.axzo.workflow.starter.mq.retry.producer.RpcInvokeEventProducer;
import cn.azxo.framework.common.model.CommonResponse;
import com.alibaba.fastjson.JSON;
import feign.Client;
import feign.MethodMetadata;
import feign.Request;
import feign.Response;
import lombok.SneakyThrows;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.HttpStatus;
import org.springframework.util.CollectionUtils;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestParam;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.Reader;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Queue;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import static cn.axzo.workflow.common.enums.RpcInvokeModeEnum.SYNC;
import static cn.axzo.workflow.common.enums.WorkflowEngineEventEnum.WORKFLOW_ENGINE_STARTER;
import static cn.axzo.workflow.starter.common.constant.StarterConstants.STARTER_INVOKE_MODE;
import static java.nio.charset.StandardCharsets.UTF_8;

/**
 * 适用于 Starter 中复合型的 FeignClient 实现
 * <p>
 * 如果使用方调用的服务方法是同步，则使用原生的 FeignClient 实现， 否则通过 MQ 事件解耦请求
 *
 * @author wangli
 * @since 2024/5/28 15:23
 */
public class ComplexInvokeClient implements Client {

    private final Logger log = LoggerFactory.getLogger(ComplexInvokeClient.class);
    private final WorkflowEngineStarterProperties starterProperties;
    private final RpcInvokeEventProducer eventProducer;
    private final Client feignClient;

    public ComplexInvokeClient(WorkflowEngineStarterProperties starterProperties,
                               EventProducer eventProducer,
                               Client feignClient) {
        this.starterProperties = starterProperties;
        this.eventProducer = (RpcInvokeEventProducer) eventProducer;
        this.feignClient = feignClient;
    }

    @Override
    public Response execute(Request request, Request.Options options) throws IOException {
        log.info("ComplexInvokeClient execute...");
        RpcInvokeModeEnum currentInvokeModeEnum = getInvokeMode(request);

        Map<String, Collection<String>> headers = request.headers();
        headers.forEach((k, v) -> log.info("ComplexInvokeClient header: {} = {}", k, v));

        if (Objects.equals(SYNC, currentInvokeModeEnum)) {
            log.info("[sync] invoke...");
            ThreadUtil.clear();
            return feignClient.execute(request, options);
        }
        asyncInvoke(request);

        return Response.builder()
                .status(HttpStatus.OK.value())
                .reason(HttpStatus.OK.getReasonPhrase())
                .headers(headers)
                .request(request)
                .body(body)
                .build();
    }

    /**
     * 发送 RPC 调用动作的 MQ 事件
     *
     * @param request
     */
    private void asyncInvoke(Request request) {
        log.info("[async] invoke...");
        WorkflowEngineStarterRpcInvokeDTO event = new WorkflowEngineStarterRpcInvokeDTO();
        MethodMetadata metadata = request.requestTemplate().methodMetadata();
        event.setClassName(metadata.targetType().getName());
        event.setMethodName(metadata.method().getName());

        List<String> args = new ArrayList<>();
        event.setParameters(args);
        buildArgs(request, metadata, args);

        eventProducer.send(WORKFLOW_ENGINE_STARTER, event);
    }

    @SneakyThrows
    private void buildArgs(Request request, MethodMetadata metadata, List<String> args) {
        Parameter[] parameters = metadata.method().getParameters();
        Annotation[][] parameterAnnotations = metadata.method().getParameterAnnotations();
        Map<String, Collection<String>> queries = request.requestTemplate().queries();
        byte[] body = request.requestTemplate().body();

        // 提前解析出 PathVariable 变量，避免后续循环时再解析
        Map<String, String> pathVariableMap = parsePathVariables(getRequestMappingsValue(metadata.method()), getUrlRemoveDomain(request));
        Queue<String> pathVariableQueue = convertMapValueToQueue(pathVariableMap);
        for (int i = 0; i < parameters.length; i++) {
            // 目前工作流的所有 API，只用了 RequestParam / RequestBody / PathVariable 三个注解，所以这里只处理这三种
            Parameter parameter = parameters[i];
            Annotation[] annotations = parameterAnnotations[i];
            for (Annotation annotation : annotations) {
                if (annotation instanceof RequestParam) {
                    // RequestParam 支持 Nullable
                    List<String> values = (List<String>) queries.getOrDefault(parameter.getName(), Collections.emptyList());
                    args.add(JSON.toJSONString(values));
                } else if (annotation instanceof PathVariable) {
                    args.add(pathVariableQueue.poll());
                } else if (annotation instanceof RequestBody) {
                    args.add(new String(body, UTF_8));
                }
            }
        }
    }

    private String getRequestMappingsValue(Method method) {
        Annotation[] annotations = method.getAnnotations();
        for (Annotation annotation : annotations) {
            if (annotation instanceof GetMapping) {
                GetMapping getMapping = (GetMapping) annotation;
                return getMapping.value()[0];
            } else if (annotation instanceof PostMapping) {
                PostMapping postMapping = (PostMapping) annotation;
                return postMapping.value()[0];
            } else if (annotation instanceof PutMapping) {
                PutMapping putMapping = (PutMapping) annotation;
                return putMapping.value()[0];
            } else if (annotation instanceof DeleteMapping) {
                DeleteMapping deleteMapping = (DeleteMapping) annotation;
                return deleteMapping.value()[0];
            }
        }
        if (log.isDebugEnabled()) {
            log.debug("{} not found request mappings", method.getName());
        }
        return "";
    }

    private String getUrlRemoveDomain(Request request) {
        return request.url().replace(request.requestTemplate().feignTarget().url(), "");
    }

    private Queue<String> convertMapValueToQueue(Map<String, String> map) {
        Queue<String> queue = new LinkedList<>();
        if (CollectionUtils.isEmpty(map)) {
            return queue;
        }
        map.forEach((k, v) -> queue.add(v));
        return queue;
    }

    private Map<String, String> parsePathVariables(String template, String actualPath) {
        Map<String, String> variables = new HashMap<>();

        // 创建一个正则表达式，用于匹配模板中的占位符
        String regex = template.replaceAll("\\{([^}]+)\\}", "([^/]+)");
        regex = regex.replaceAll("/+", "/"); // 去除可能由于连续占位符产生的多余斜杠
        regex = "^" + regex + "$"; // 锚定整个字符串的匹配

        Pattern pattern = Pattern.compile(regex);
        Matcher matcher = pattern.matcher(actualPath);

        if (matcher.find()) {
            // 提取占位符名称和对应的值
            Pattern variablePattern = Pattern.compile("\\{([^}]+)\\}");
            Matcher variableMatcher = variablePattern.matcher(template);
            int index = 1; // 匹配值的索引从1开始（0是整个匹配的字符串）
            while (variableMatcher.find()) {
                variables.put(variableMatcher.group(1), matcher.group(index++));
            }
        }

        return variables;
    }

    private RpcInvokeModeEnum getInvokeMode(Request request) {
        Collection<String> invokeModel = request.headers().getOrDefault(STARTER_INVOKE_MODE,
                Collections.singletonList(starterProperties.getInvokeMode().name()));
        if (CollectionUtils.isEmpty(invokeModel)) {
            return starterProperties.getInvokeMode();
        } else if (invokeModel.size() > 1) {
            throw new WorkflowEngineStarterException("Multiple invoke mode is not supported");
        }
        return RpcInvokeModeEnum.valueOf(invokeModel.iterator().next());
    }

    static Response.Body body = new Response.Body() {
        final ByteArrayInputStream inputStream = new ByteArrayInputStream(JSON.toJSONString(CommonResponse.success(HttpStatus.OK.value(), "Send MQ Success", null))
                .getBytes(UTF_8));

        @Override
        public Integer length() {
            return null;
        }

        @Override
        public boolean isRepeatable() {
            return false;
        }

        @Override
        public InputStream asInputStream() throws IOException {
            return inputStream;
        }

        @Override
        public Reader asReader(Charset charset) throws IOException {
            return new InputStreamReader(asInputStream(), charset);
        }

        @Override
        public void close() throws IOException {
            inputStream.close();
        }
    };
}
