package cn.axzo.msg.center.utils;

import cn.axzo.msg.center.common.utils.PlaceholderResolver;
import cn.axzo.msg.center.inside.notices.config.PendingMessageBizConfig;
import cn.axzo.msg.center.message.domain.dto.MessageTemplateDTO;
import cn.axzo.msg.center.message.domain.dto.MessageTemplateRouterDTO;
import cn.axzo.msg.center.message.domain.dto.MessageTemplateRouterDTO.MessageRouteButtonDTO;
import cn.axzo.msg.center.message.domain.dto.MessageTemplateRouterDTO.MessageRouteDetailDTO;
import cn.axzo.msg.center.message.domain.dto.MessageTemplateRouterDTO.MessageRouterConfigDTO;
import cn.axzo.msg.center.message.service.todo.manage.TodoExt;
import cn.axzo.msg.center.service.dto.ButtonRouterDTO;
import cn.axzo.msg.center.service.dto.DetailRouterDTO;
import cn.axzo.msg.center.service.dto.MessageButtonRouteStrategyDTO;
import cn.axzo.msg.center.service.dto.MessageDetailRouteStrategyDTO;
import cn.axzo.msg.center.service.enums.ButtonStyleEnum;
import cn.axzo.msg.center.service.enums.PresetButtonType;
import cn.axzo.msg.center.service.enums.RouterCategoryEnum;
import cn.axzo.msg.center.service.enums.TerminalTypeEnum;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Component;

import javax.annotation.Nullable;
import java.net.URLEncoder;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.function.Consumer;
import java.util.function.Supplier;
import java.util.stream.Collectors;

/**
 * @description
 *   消息路由工具方法
 *
 * @author cold_blade
 * @date 2023/9/28
 * @version 1.0
 */
@Slf4j
@Component
@RequiredArgsConstructor
public final class MessageRouterUtil {

    public static final String DETAIL_ROUTER_DESC = "详情";

    private final PendingMessageBizConfig cfg;

    /**
     * 获取业务详情路由策略
     *
     * @param msgTemplateRouter  模板路由配置
     * @return 业务展示详情策略
     */
    public static Optional<MessageDetailRouteStrategyDTO> fetchBizDetailShowStrategy(MessageTemplateRouterDTO msgTemplateRouter) {
        if (Objects.isNull(msgTemplateRouter)) {
            return Optional.empty();
        }
        return Optional.ofNullable(msgTemplateRouter.getRouteDetail())
                .map(MessageRouteDetailDTO::toBizDetailShowStrategyDTO);
    }

    /**
     * 获取业务详情路由
     *
     * @param msgTemplateRouter  模板路由配置
     * @return 业务展示详情策略
     */
    public static Optional<DetailRouterDTO> fetchBizDetailRouter(MessageTemplateRouterDTO msgTemplateRouter,
                                                                 TerminalTypeEnum terminalType) {
        if (terminalType == null) {
            return Optional.empty();
        }
        if (Objects.isNull(msgTemplateRouter)) {
            return Optional.empty();
        }
        return Optional.ofNullable(msgTemplateRouter.getRouteDetail())
                .map(v -> convert(v, terminalType));
    }

    /**
     * 获取业务按钮路由策略
     *
     * @param msgTemplateRouter  模板路由配置
     * @return 业务按钮路由策略
     */
    public static List<MessageButtonRouteStrategyDTO> fetchMessageRouterButtonStrategies(MessageTemplateRouterDTO msgTemplateRouter) {
        if (Objects.isNull(msgTemplateRouter)) {
            return Collections.emptyList();
        }
        if (CollectionUtils.isEmpty(msgTemplateRouter.getRouteButtons())) {
            return Collections.emptyList();
        }
        return msgTemplateRouter.getRouteButtons().stream()
                .map(MessageRouteButtonDTO::toMessageRouterButton)
                .collect(Collectors.toList());
    }

    /**
     * 获取业务按钮路由
     *
     * @param msgTemplateRouter  模板路由配置
     * @return 业务按钮路由
     */
    public static List<ButtonRouterDTO> fetchMessageRouterButtons(MessageTemplateRouterDTO msgTemplateRouter,
                                                                  TerminalTypeEnum terminalType) {
        if (terminalType == null) {
            return Collections.emptyList();
        }
        if (Objects.isNull(msgTemplateRouter)) {
            return Collections.emptyList();
        }
        if (CollectionUtils.isEmpty(msgTemplateRouter.getRouteButtons())) {
            return Collections.emptyList();
        }
        return msgTemplateRouter.getRouteButtons().stream()
                .sorted(Comparator.comparing(MessageRouteButtonDTO::getPriority))
                .map(e -> convert(e, terminalType))
                .collect(Collectors.toList());
    }

    /**
     * 解析按钮风格的存储模型
     *
     * @param styleObj 按钮style对象
     * @return 按钮style列表
     */
    public static List<ButtonStyleEnum> parseButtonStyle(JSONArray styleObj) {
        if (Objects.isNull(styleObj)) {
            return Collections.emptyList();
        }
        return JSON.parseArray(styleObj.toJSONString(), ButtonStyleEnum.class);
    }

    /**
     * 解析模板上配置的路由地址,将发送消息时的参数替换上去，并将路由参数追加到模板的路由地址后面，兼容APP端新老版本
     *
     * @param msgTemplateRouter 路由信息
     * @param routerParam  路由参数
     * @return MessageTemplateRouterDTO
     */
    public MessageTemplateRouterDTO parseRouteUrl(MessageTemplateRouterDTO msgTemplateRouter,
                                                         JSONObject routerParam) {
        if (Objects.isNull(msgTemplateRouter)) {
            return msgTemplateRouter;
        }
        // 拷贝一份，避免修改入参
        msgTemplateRouter = msgTemplateRouter.deepClone();
        // 路由参数有效
        if (Objects.nonNull(routerParam)) {
            // 编排业务详情路由
            parseAndConcatDetailRouterUrl(msgTemplateRouter.getRouteDetail(), routerParam, false);
            // 编排路由按钮
            parseAndConcatButtonRouterUrl(msgTemplateRouter.getRouteButtons(), routerParam, false);
        }
        return msgTemplateRouter;
    }

    /**
     * 解析模板上配置的路由地址,将发送消息时的参数替换上去，并将路由参数追加到模板的路由地址后面，兼容APP端新老版本
     *
     * @param template
     * @param msgTemplateRouter 路由信息
     * @param routerParam       路由参数
     * @return MessageTemplateRouterDTO
     */
    public MessageTemplateRouterDTO parseAndConcatRouteUrl(MessageTemplateDTO template,
                                                           MessageTemplateRouterDTO msgTemplateRouter,
                                                           JSONObject routerParam) {
        if (Objects.isNull(msgTemplateRouter)) {
            return msgTemplateRouter;
        }
        // 拷贝一份，避免修改入参
        msgTemplateRouter = msgTemplateRouter.deepClone();
        // 路由参数有效
        if (Objects.nonNull(routerParam)) {
            boolean isConcatRouterParams = cfg.isConcatRouterParams(template.getCode());
            // 编排业务详情路由
            parseAndConcatDetailRouterUrl(msgTemplateRouter.getRouteDetail(), routerParam, isConcatRouterParams);
            // 编排路由按钮
            parseAndConcatButtonRouterUrl(msgTemplateRouter.getRouteButtons(), routerParam, isConcatRouterParams);
        }
        return msgTemplateRouter;
    }

    private static void parseAndConcatDetailRouterUrl(MessageRouteDetailDTO routeDetail, JSONObject routerParam,
                                                      boolean isConcat) {
        if (Objects.isNull(routeDetail)) {
            return;
        }
        routeDetail.getRouterConfigs()
                .forEach(e -> concatRouterParam(e::getUrl, e::setUrl, routerParam, isConcat));
    }

    private static void parseAndConcatButtonRouterUrl(List<MessageRouteButtonDTO> routeButtons, JSONObject routerParam,
                                                      boolean isConcat) {
        if (CollectionUtils.isEmpty(routeButtons)) {
            return;
        }
        routeButtons.forEach(e -> {
            if (StringUtils.isNotBlank(e.getApiUrl())) {
                // API调用类型的按钮
                concatRouterParam(e::getApiUrl, e::setApiUrl, routerParam, isConcat);
            }
            if (CollectionUtils.isNotEmpty(e.getRouterConfigs())) {
                // 页面跳转类型的按钮
                e.getRouterConfigs()
                        .forEach(config -> concatRouterParam(config::getUrl, config::setUrl, routerParam, isConcat));
            }
        });
    }

    private static void concatRouterParam(Supplier<String> getUrlSupplier, Consumer<String> setUrlConsumer,
                                          JSONObject routerParam, boolean isConcat) {
        JSONObject copy = (JSONObject)routerParam.clone();
        // 对第一层的值进行编码
        //for (String key : new HashSet<>(copy.keySet())) {
        //    Object value = copy.get(key);
        //    copy.put(key, tryEncode(value));
        //}
        // 替换原始URL中的参数变量
        String routerUrl = PlaceholderResolver.getDefaultResolver().resolveByMap(getUrlSupplier.get(), copy);
        // 将routerParam追加到原始的URL后面
        if (isConcat) {
            routerUrl = concatRouterParam(routerUrl, routerParam);
        }
        setUrlConsumer.accept(routerUrl);
    }

    private static String concatRouterParam(String originalUrl, JSONObject routerParam) {
        JSONObject copy = (JSONObject)routerParam.clone();
        Object ctx = copy.remove(TodoExt.CTX);
        try {
            if (copy.isEmpty()) {
                return originalUrl;
            }
            StringBuilder paramBuilder = new StringBuilder();
            for (Map.Entry<String, Object> entry : copy.entrySet()) {
                if (originalUrl.contains(entry.getKey() + "=")) {
                    continue;
                }
                String value = tryEncode(entry.getValue());
                paramBuilder.append("&").append(entry.getKey()).append("=").append(value);
            }
            if (StringUtils.isBlank(paramBuilder.toString())) {
                return originalUrl;
            }
            if (!originalUrl.contains("?")) {
                // 替换掉第一个参数分隔符(&)
                paramBuilder.replace(0, 1, "?");
            }
            return originalUrl + paramBuilder;
        } finally {
            if (ctx != null) {
                copy.put(TodoExt.CTX, ctx);
            }
        }
    }

    private static String tryEncode(Object o) {
        String value = String.valueOf(o);
        try {
            value = URLEncoder.encode(value, "UTF-8");
        } catch (Exception e) {
            log.warn("error encode pending dynamic value. value={}", value);
        }
        return value;
    }

    private static DetailRouterDTO convert(MessageRouteDetailDTO detailRouteStrategy, TerminalTypeEnum terminalType) {
        MessageRouterConfigDTO routerTerminal = select(detailRouteStrategy.getRouterConfigs(), terminalType);
        if (routerTerminal == null) {
            log.info("cant find router config for route detail. templateCode={}", detailRouteStrategy.getTemplateCode());
        }
        return DetailRouterDTO.builder()
                .showStrategy(detailRouteStrategy.getShowStrategy())
                .jumpStrategy(detailRouteStrategy.getJumpStrategy())
                .url(routerTerminal == null ? null : routerTerminal.getUrl())
                .terminalType(routerTerminal == null ? null : routerTerminal.getTerminalType())
                .build();
    }

    private static ButtonRouterDTO convert(MessageRouteButtonDTO routeButton, TerminalTypeEnum terminalType) {
        Supplier<String> nameFun = () -> {
            if (StringUtils.isNotBlank(routeButton.getName()))
                return routeButton.getName();
            PresetButtonType buttonType = routeButton.getPresetButtonType();
            return buttonType == null ? null : buttonType.getDesc();
        };
        if (RouterCategoryEnum.ACTION.equals(routeButton.getCategory())) {
            return ButtonRouterDTO.builder()
                    .key(routeButton.getBtnCode())
                    .presetButtonType(routeButton.getPresetButtonType())
                    .source(routeButton.getSource())
                    .desc(nameFun.get())
                    .style(parseButtonStyle(routeButton.getStyle()))
                    .executorShow(routeButton.getExecutorShow())
                    .pendingShow(routeButton.getPendingShow())
                    .category(routeButton.getCategory())
                    .url(routeButton.getApiUrl())
                    .build();
        }
        MessageRouterConfigDTO routerConfig = select(routeButton.getRouterConfigs(), terminalType);
        if (routerConfig == null) {
            log.info("cant find router config for route button. templateCode={}, btnCode={}",
                    routeButton.getTemplateCode(), routeButton.getBtnCode());
        }
        return ButtonRouterDTO.builder()
                .key(routeButton.getBtnCode())
                .presetButtonType(routeButton.getPresetButtonType())
                .source(routeButton.getSource())
                .desc(nameFun.get())
                .style(parseButtonStyle(routeButton.getStyle()))
                .executorShow(routeButton.getExecutorShow())
                .pendingShow(routeButton.getPendingShow())
                .category(routeButton.getCategory())
                .url(routerConfig == null ? null : routerConfig.getUrl())
                .terminalType(routerConfig == null ? null : routerConfig.getTerminalType())
                .build();
    }

    @Nullable
    private static MessageRouterConfigDTO select(List<MessageRouterConfigDTO> routerTerminals,
                                                 TerminalTypeEnum terminalType) {
        Supplier<MessageRouterConfigDTO> defaultFun = () ->
                CollectionUtils.isEmpty(routerTerminals)
                        ? null
                        : routerTerminals.get(0);
        return routerTerminals.stream()
                .filter(e -> Objects.equals(terminalType, e.getTerminalType()))
                .findFirst()
                // 优先选择H5，没有则选择第一个元素
                .orElseGet(() -> selectH5(routerTerminals).orElseGet(defaultFun));
    }

    private static Optional<MessageRouterConfigDTO> selectH5(List<MessageRouterConfigDTO> routerTerminals) {
        return routerTerminals.stream()
                .filter(e -> Objects.equals(TerminalTypeEnum.WEB_VIEW, e.getTerminalType()))
                .findFirst();
    }
}
