package com.msl.common.open;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.TypeReference;
import com.google.common.reflect.TypeToken;
import com.google.gson.*;
import com.msl.common.open.action.Action;
import com.msl.common.open.message.Message;
import com.msl.common.result.Result;
import com.msl.common.utils.EncryptUtil;
import lombok.extern.slf4j.Slf4j;

import java.lang.reflect.Type;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.time.format.DateTimeParseException;

/**
 * @author wanglq
 * Date 2024/4/25
 * Time 20:31
 */
@Slf4j
@SuppressWarnings("all")
public class OpenClient {

    /**
     * 执行action
     *
     * @param action action
     * @param <T>    返回类型
     * @return 结果
     */
    public static <T> Result<T> doAction(RequestConfig config, Action<T> action) {
        return doPost(config, action, "Action");
    }

    /**
     * 发送消息
     *
     * @param message 消息
     * @param <T>     返回类型
     * @return 结果
     */
    public static <T> Result<T> sendMessage(RequestConfig config, Message<T> message) {
        return doPost(config, message, "Message");
    }

    /**
     * 返回业务结果转换
     *
     * @param result      业务结果
     * @param targetClass 目标类型
     * @param <T>         目标类型
     * @return 结果实体
     */
    private static <T> Result<T> covertResult(String result, Type targetClass) {
        Gson gson = new GsonBuilder()
                .registerTypeAdapter(LocalDateTime.class, new CustomLocalDateTimeDeserializer())
                .create();
        // 使用TypeToken来获取带有泛型信息的Type
        Type type = new TypeToken<Result<T>>() {
        }.getType();
        Result<T> resultObject = gson.fromJson(result, type);

        // 如果目标类型不为空，则尝试将data属性转换为目标类型
        if (targetClass != null && resultObject.getData() != null) {
            T dataObject = gson.fromJson(gson.toJson(resultObject.getData()), targetClass);
            resultObject.setData(dataObject);
        }
        return resultObject;
    }

    /**
     * 执行post请求
     *
     * @param openDto 请求参数
     * @since 1.1.0
     */
    private static <T> Result<T> doPost(RequestConfig config, Process<T> process, String type) {
        try {
            String str = JSON.toJSONString(process);
            ProcessId processId = process.processId();
            log.info("OpenClient发送 {}<{}> 请求参数：{}", type, processId.show(), str);

            //构造加密实体
            OpenDto openDto = OpenDto.buildEncrypt(config.getAppId(), str, config.getAppKey(), System.currentTimeMillis())
                    .setNamespace(processId.namespace())
                    .setOperate(processId.identity());

            //执行请求
            return HttpExecutor.post(config.getGatewayUrl(), null,
                            openDto)
                    //反序化
                    .map(r -> JSON.parseObject(r, new TypeReference<OpenDto>() {
                    }))
                    //数据解密
                    .mapTry(dto -> EncryptUtil.decrypt(dto.getData(), config.getAppKey()))
                    .peek(s -> log.info("{}<{}> 返回结果：{}", type, processId.show(), s))
                    //业务结果实体转换
                    .map(s -> {
                        Result<T> r = covertResult(s, process.returnType());
                        return r;
                    }).orElse(Result.fail());
        } catch (Exception e) {
            throw new RuntimeException("OpenClient请求失败", e);
        }
    }

    public static class CustomLocalDateTimeDeserializer implements JsonDeserializer<LocalDateTime> {
        private final DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");

        @Override
        public LocalDateTime deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
            try {
                return LocalDateTime.parse(json.getAsString(), formatter);
            } catch (DateTimeParseException e) {
                throw new JsonParseException("Date parse error", e);
            }
        }
    }

}
