package cn.axzo.msg.center.message.xxl;

import cn.axzo.maokai.api.util.Ref;
import cn.axzo.maokai.api.vo.response.tree.ValueNode;
import cn.axzo.msg.center.dal.MessageBaseTemplateDao;
import cn.axzo.msg.center.dal.MessageTemplateButtonV3Dao;
import cn.axzo.msg.center.dal.MessageTemplateGroupDao;
import cn.axzo.msg.center.dal.MessageTemplateGroupV3Dao;
import cn.axzo.msg.center.dal.MessageTemplateV3Dao;
import cn.axzo.msg.center.domain.entity.BaseEntityWithOperator;
import cn.axzo.msg.center.domain.entity.MessageBaseTemplate;
import cn.axzo.msg.center.domain.entity.MessageTemplateButtonV3;
import cn.axzo.msg.center.domain.entity.MessageTemplateGroup;
import cn.axzo.msg.center.domain.entity.MessageTemplateGroupV3;
import cn.axzo.msg.center.domain.entity.MessageTemplateV3;
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.MessageTemplateNewService;
import cn.axzo.msg.center.message.service.group.GroupTemplateService;
import cn.axzo.msg.center.message.service.group.NodeWrapper;
import cn.axzo.msg.center.message.service.group.RootNodeWrapper;
import cn.axzo.msg.center.service.domain.CardUrlConfig;
import cn.axzo.msg.center.service.domain.DetailConfig;
import cn.axzo.msg.center.service.domain.GroupConfig;
import cn.axzo.msg.center.service.domain.KVConfig;
import cn.axzo.msg.center.service.domain.UrlConfig;
import cn.axzo.msg.center.service.domain.url.AppUrl;
import cn.axzo.msg.center.service.domain.url.WebUrl;
import cn.axzo.msg.center.service.dto.MessageCardContentItemDTO;
import cn.axzo.msg.center.service.enums.AppTerminalTypeEnum;
import cn.axzo.msg.center.service.enums.BizDetailJumpStrategyEnum;
import cn.axzo.msg.center.service.enums.BizDetailShowStrategyEnum;
import cn.axzo.msg.center.service.enums.CardUrlOpenStrategy;
import cn.axzo.msg.center.service.enums.GroupType;
import cn.axzo.msg.center.service.enums.KVContentType;
import cn.axzo.msg.center.service.enums.MessageCategoryEnum;
import cn.axzo.msg.center.service.enums.PushTerminalEnum;
import cn.axzo.msg.center.service.enums.RouterButtonSourceEnum;
import cn.axzo.msg.center.service.enums.RouterCategoryEnum;
import cn.axzo.msg.center.service.enums.TerminalTypeEnum;
import cn.axzo.msg.center.service.enums.WebPageOpenStrategy;
import cn.axzo.msg.center.service.enums.YesOrNo;
import cn.axzo.msg.center.utils.AsyncRunTasks;
import cn.axzo.msg.center.utils.JSONObjectUtil;
import com.alibaba.fastjson.JSON;
import com.xxl.job.core.biz.model.ReturnT;
import com.xxl.job.core.handler.annotation.XxlJob;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Component;
import org.springframework.transaction.support.TransactionTemplate;
import org.springframework.util.ReflectionUtils;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.concurrent.Executors;
import java.util.concurrent.Semaphore;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Supplier;

import static java.util.stream.Collectors.toList;
import static java.util.stream.Collectors.toSet;

/**
 * @author yanglin
 */
@Slf4j
@Component
@RequiredArgsConstructor
public class MigrateMessageTemplateV3Job {

    private final TransactionTemplate transactionTemplate;
    private final MessageBaseTemplateDao messageBaseTemplateDao;
    private final MessageTemplateNewService messageTemplateNewService;
    private final MessageTemplateV3Dao messageTemplateV3Dao;
    private final GroupTemplateService groupTemplateService;
    private final MessageTemplateGroupV3Dao messageTemplateGroupV3Dao;
    private final MessageTemplateButtonV3Dao messageTemplateButtonV3Dao;
    private final MessageTemplateGroupDao messageTemplateGroupDao;
    private final PendingMessageBizConfig pendingMessageBizConfig;

    @SuppressWarnings("unused")
    @XxlJob("migrateMessageTemplateV3Job")
    public ReturnT<String> execute(String jsonStr) throws Exception {
        try {
            Param param = StringUtils.isBlank(jsonStr)
                    ? new Param()
                    : JSONObjectUtil.parseObject(jsonStr, Param.class);
            return executeImpl(param);
        } catch (Exception e) {
            log.warn("job failed", e);
            throw e;
        }
    }

    private ReturnT<String> executeImpl(Param param) throws Exception {
        RootNodeWrapper groupRoot = groupTemplateService.getGroupRoot();
        List<MessageBaseTemplate> baseTemplates = messageBaseTemplateDao.lambdaQuery()
                .eq(MessageBaseTemplate::getIsDelete, 0)
                .in(CollectionUtils.isNotEmpty(param.getTemplateCodes()),
                        MessageBaseTemplate::getCode, param.getTemplateCodes())
                .list();
        int nThreads = 10;
        AsyncRunTasks tasks = new AsyncRunTasks(Executors.newFixedThreadPool(nThreads));
        Semaphore semaphore = new Semaphore(nThreads);
        for (int i = 0; i < baseTemplates.size(); i++) {
            MessageBaseTemplate baseTemplate = baseTemplates.get(i);
            log.info("migrating template: {}/{}", i + 1, baseTemplates.size());
            MessageTemplateV3 templateV3 = messageTemplateV3Dao.findByCode(baseTemplate.getCode()).orElse(null);
            if (templateV3 != null) continue;
            semaphore.acquire();
            tasks.runAsync(() -> {
                try {
                    transactionTemplate.executeWithoutResult(unused -> migrateTemplate(groupRoot, baseTemplate));
                } catch (Exception e) {
                    throw new RuntimeException(e);
                } finally {
                    semaphore.release();
                }
            });
        }
        tasks.awaitTermination();
        return ReturnT.SUCCESS;
    }

    private void migrateTemplate(RootNodeWrapper groupRoot, MessageBaseTemplate baseTemplate) {
        MessageTemplateDTO baseTemplateModel = messageTemplateNewService
                .listByTemplateCodes(Collections.singletonList(baseTemplate.getCode()))
                .get(0);

        MessageTemplateV3 templateV3 = new MessageTemplateV3();
        templateV3.setName(baseTemplate.getName());
        templateV3.setCode(baseTemplate.getCode());
        templateV3.setMsgCategory(baseTemplate.getMsgCategory());
        templateV3.setTitle(baseTemplate.getTitle());
        templateV3.setContent(baseTemplate.getContent());
        templateV3.setDetailStyleCode(baseTemplate.getDetailStyleCode());
        templateV3.setIcon(baseTemplate.getIcon());
        templateV3.setMinAppVersion(baseTemplate.getMinAppVersion());
        templateV3.setStatus(baseTemplate.getStatus());
        templateV3.setPushTerminal(baseTemplate.getPushTerminal());
        templateV3.setPushData(baseTemplate.getPushData());
        templateV3.setImSendPriority(baseTemplate.getImSendPriority());
        templateV3.setDisplayOnList(YesOrNo.YES);
        templateV3.setIsDelete(0L);
        templateV3.setCreateAt(baseTemplate.getCreateAt());
        templateV3.getOrCreateRecordExt().setIsMigrated(true);
        setOperatorAsSystem(templateV3);

        Runnable defaultCardUrlSetter = () -> templateV3.setCardUrlOpenStrategy(
                baseTemplate.getMsgCategory() == MessageCategoryEnum.GENERAL_MESSAGE
                        ? CardUrlOpenStrategy.NONE
                        : CardUrlOpenStrategy.OPEN_TODO_DETAIL);
        List<MessageTemplateButtonV3> buttons = null;
        MessageTemplateRouterDTO baseTemplateRouter = baseTemplateModel.getMsgTemplateRouter();
        if (baseTemplateRouter != null) {
            if (baseTemplateRouter.getRouteDetail() != null)
                configureCardUrl(groupRoot, baseTemplate, baseTemplateRouter.getRouteDetail(), templateV3);
            else
                defaultCardUrlSetter.run();
            if (baseTemplateRouter.getRouteButtons() != null)
                buttons = migrateButtons(groupRoot, baseTemplate, baseTemplateRouter.getRouteButtons());
        } else {
            defaultCardUrlSetter.run();
        }

        MessageTemplateGroupV3 group = migrateGroups(groupRoot, baseTemplate, baseTemplateModel);

        messageTemplateV3Dao.save(templateV3);
        if (CollectionUtils.isNotEmpty(buttons))
            messageTemplateButtonV3Dao.saveBatch(buttons);
        if (group != null)
            messageTemplateGroupV3Dao.save(group);
    }

    private void configureCardUrl(RootNodeWrapper groupRoot,
                                  MessageBaseTemplate baseTemplate,
                                  MessageRouteDetailDTO routeDetail,
                                  MessageTemplateV3 templateV3) {
        Runnable cardUrlBuilder = () -> {
            CardUrlConfig cardUrConfig = new CardUrlConfig();
            cardUrConfig.setUrlConfig(buildUrlConfig(groupRoot, baseTemplate, routeDetail.getRouterConfigs()));
            templateV3.setCardUrlConfig(cardUrConfig);
        };
        if (baseTemplate.getMsgCategory() == MessageCategoryEnum.GENERAL_MESSAGE) {
            templateV3.setCardUrlOpenStrategy(CardUrlOpenStrategy.OPEN_CUSTOM_PAGE);
            cardUrlBuilder.run();
        } else {
            if (routeDetail.getShowStrategy() == BizDetailShowStrategyEnum.INLINE) {
                templateV3.setCardUrlOpenStrategy(CardUrlOpenStrategy.OPEN_TODO_DETAIL);
            } else {
                if (routeDetail.getJumpStrategy() == BizDetailJumpStrategyEnum.BUSINESS) {
                    templateV3.setCardUrlOpenStrategy(CardUrlOpenStrategy.OPEN_CUSTOM_PAGE);
                    cardUrlBuilder.run();
                } else {
                    templateV3.setCardUrlOpenStrategy(CardUrlOpenStrategy.OPEN_TODO_DETAIL);
                }
            }
        }
    }

    private List<MessageTemplateButtonV3> migrateButtons(RootNodeWrapper groupRoot,
                                                         MessageBaseTemplate baseTemplate,
                                                         List<MessageRouteButtonDTO> routeButtons) {
        if (CollectionUtils.isEmpty(routeButtons)) return Collections.emptyList();
        ArrayList<MessageTemplateButtonV3> buttons = new ArrayList<>();
        for (MessageRouteButtonDTO routeButton : routeButtons) {
            if (routeButton.getBtnCode().equals("BPMN_COPY")) {
                // 跳过流程的抄送按钮，他们的链接没有配置，保存的时候校验通不过
                continue;
            }
            MessageTemplateButtonV3 button = new MessageTemplateButtonV3();
            buttons.add(button);
            button.setName(routeButton.getName());
            button.setCode(routeButton.getBtnCode());
            button.setTemplateCode(baseTemplate.getCode());
            button.setPresetBtnType(routeButton.getPresetButtonType());
            button.setSource(routeButton.getSource());
            button.setCategory(routeButton.getCategory());
            button.setApiUrl(routeButton.getApiUrl());
            button.setStyle(routeButton.getStyle());
            button.setExecutorShow(routeButton.getExecutorShow());
            button.setPendingShow(routeButton.getPendingShow());
            button.setPriority(routeButton.getPriority());
            button.getOrCreateRecordExt().setIsMigrated(true);
            setOperatorAsSystem(button);
            if (routeButton.getCategory() != RouterCategoryEnum.JUMP)
                continue;
            if (baseTemplate.getMsgCategory() == MessageCategoryEnum.APPROVAL_PENDING_MESSAGE
                    && routeButton.getSource() == RouterButtonSourceEnum.SYSTEM) {
                Ref<Boolean> urlSet = Ref.create(false);
                Consumer<TerminalTypeEnum> approvalButtonBuilder = terminalType -> {
                    if (urlSet.get()) return;
                    searchRouter(routeButton.getRouterConfigs(), terminalType).ifPresent(router -> {
                        UrlConfig urlConfig = new UrlConfig();
                        urlConfig.setDefaultUrl(router.getUrl());
                        button.setUrlConfig(urlConfig);
                        urlSet.set(true);
                    });
                };
                approvalButtonBuilder.accept(TerminalTypeEnum.WEB_VIEW);
            } else {
                button.setUrlConfig(buildUrlConfig(groupRoot, baseTemplate, routeButton.getRouterConfigs()));
            }
        }
        return buttons;
    }

    private MessageTemplateGroupV3 migrateGroups(RootNodeWrapper groupRoot,
                                                 MessageBaseTemplate baseTemplate,
                                                 MessageTemplateDTO baseTemplateModel) {

        if (StringUtils.isBlank(baseTemplate.getCardContent())) return null;

        List<MessageCardContentItemDTO> cardItems = JSONObjectUtil
                .parseArray(baseTemplate.getCardContent(), MessageCardContentItemDTO.class);

        cardItems = cardItems.stream()
                // 工人卡片的去掉，产品说他去配置
                .filter(cardItem -> !cardItem.getLabel().equals("customerPersonId"))
                .collect(toList());

        if (CollectionUtils.isEmpty(cardItems)) return null;

        GroupConfig groupConfig = new GroupConfig();
        groupConfig.setKeyValues(new ArrayList<>());
        for (int i = 0; i < cardItems.size(); i++) {
            MessageCardContentItemDTO cardItem = cardItems.get(i);
            KVConfig kv = new KVConfig();
            kv.setContentType(KVContentType.TEXT);
            kv.setKey(cardItem.getLabel());
            kv.setValue(cardItem.getValue());
            kv.setDisplayOnCard(i < 3);
            groupConfig.getKeyValues().add(kv);
        }

        Supplier<MessageRouteDetailDTO> detailFactory = () -> {
            MessageTemplateRouterDTO templateRouter = baseTemplateModel.getMsgTemplateRouter();
            if (templateRouter == null) return null;
            return templateRouter.getRouteDetail();
        };

        if (detailFactory.get() != null
                && detailFactory.get().getShowStrategy() == BizDetailShowStrategyEnum.JUMP_TO) {
            DetailConfig detailConfig = new DetailConfig();
            detailConfig.setName("查看详情");
            detailConfig.setUrlConfig(buildUrlConfig(
                    groupRoot, baseTemplate, detailFactory.get().getRouterConfigs()));
            groupConfig.setDetail(detailConfig);
        }

        MessageTemplateGroupV3 group = new MessageTemplateGroupV3();
        group.setName("基本信息");
        group.setTemplateCode(baseTemplate.getCode());
        group.setSortOrder(1);
        group.setGroupType(GroupType.KV_VALUES);
        group.setGroupConfig(groupConfig);
        group.getOrCreateRecordExt().setIsMigrated(true);
        setOperatorAsSystem(group);
        return group;
    }

    private UrlConfig buildUrlConfig(RootNodeWrapper groupRoot,
                                     MessageBaseTemplate baseTemplate,
                                     List<MessageRouterConfigDTO> routers) {
        UrlConfig urlConfig = new UrlConfig();

        searchRouter(routers, TerminalTypeEnum.WEB).ifPresent(router -> {
            WebUrl pcCms = new WebUrl();
            pcCms.setOpenStrategy(WebPageOpenStrategy.CURRENT_PAGE);
            pcCms.setUrl(router.getUrl());
            urlConfig.setPcCms(pcCms);

            WebUrl pcOms = new WebUrl();
            pcOms.setOpenStrategy(WebPageOpenStrategy.CURRENT_PAGE);
            pcOms.setUrl(router.getUrl());
            urlConfig.setPcOms(pcOms);
        });
        List<PushTerminalEnum> terminals = determineTerminalTypes(groupRoot, baseTemplate);
        searchRouter(routers, TerminalTypeEnum.IOS).ifPresent(router -> {
            AppUrl ios = new AppUrl();
            ios.setUrl(router.getUrl());
            if (terminals.contains(PushTerminalEnum.B_ENTERPRISE_APP))
                urlConfig.getOrCreateAppManager().setIos(ios);
            if (terminals.contains(PushTerminalEnum.C_WORKER_APP))
                urlConfig.getOrCreateAppWorker().setIos(ios);
        });
        searchRouter(routers, TerminalTypeEnum.ANDROID).ifPresent(router -> {
            AppUrl android = new AppUrl();
            android.setUrl(router.getUrl());
            if (terminals.contains(PushTerminalEnum.B_ENTERPRISE_APP))
                urlConfig.getOrCreateAppManager().setAndroid(android);
            if (terminals.contains(PushTerminalEnum.C_WORKER_APP))
                urlConfig.getOrCreateAppWorker().setAndroid(android);
        });
        searchRouter(routers,
                TerminalTypeEnum.WEB_VIEW,
                TerminalTypeEnum.MINI_PROGRAM,
                TerminalTypeEnum.WECHAT_MINI_PROGRAM).ifPresent(router -> {
            AppUrl app = new AppUrl();
            app.setUrl(router.getUrl());
            if (terminals.contains(PushTerminalEnum.B_ENTERPRISE_APP)) {
                urlConfig.getOrCreateAppManager().setIos(app);
                urlConfig.getOrCreateAppManager().setAndroid(app);
            }
            if (terminals.contains(PushTerminalEnum.C_WORKER_APP)) {
                urlConfig.getOrCreateAppWorker().setIos(app);
                urlConfig.getOrCreateAppWorker().setAndroid(app);
            }
        });

        boolean anyFieldSet = Arrays.stream(UrlConfig.class.getDeclaredFields())
                .anyMatch(field -> {
                    field.setAccessible(true);
                    return ReflectionUtils.getField(field, urlConfig) != null;
                });
        return anyFieldSet ? urlConfig : null;
    }

    private Optional<MessageRouterConfigDTO> searchRouter(List<MessageRouterConfigDTO> routers,
                                                          TerminalTypeEnum... terminalTypes) {
        List<TerminalTypeEnum> terminalTypesList = Arrays.asList(terminalTypes);
        return routers.stream()
                .filter(r -> terminalTypesList.contains(r.getTerminalType()))
                .findFirst();
    }

    private List<PushTerminalEnum> determineTerminalTypes(RootNodeWrapper groupRoot,
                                                          MessageBaseTemplate baseTemplate) {
        if (baseTemplate.getMsgCategory() == MessageCategoryEnum.GENERAL_MESSAGE) {
            return JSON.parseArray(baseTemplate.getPushTerminal(), PushTerminalEnum.class);
        }
        Set<String> paths = messageTemplateGroupDao.lambdaQuery()
                .eq(MessageTemplateGroup::getTemplateCode, baseTemplate.getCode())
                .list().stream()
                .map(MessageTemplateGroup::getPath)
                .collect(toSet());
        Map<AppTerminalTypeEnum, List<Long>> appType2NodeIds = pendingMessageBizConfig.getMsgGroupConfig();
        Function<AppTerminalTypeEnum, Boolean> appSelector = appType -> {
            for (Map.Entry<AppTerminalTypeEnum, List<Long>> e : appType2NodeIds.entrySet()) {
                if (appType != e.getKey())
                    continue;
                for (Long parentNodeId : e.getValue()) {
                    ValueNode<NodeWrapper> parentNode = groupRoot
                            .findValueNode(parentNodeId).orElse(null);
                    if (parentNode == null) continue;
                    String code = parentNode.getValue().unwrap().getCode();
                    if (paths.stream().anyMatch(path -> path.contains(code)))
                        return true;
                }
            }
            return false;
        };
        ArrayList<PushTerminalEnum> terminals = new ArrayList<>();
        if (appSelector.apply(AppTerminalTypeEnum.C_WORKER_APP))
            terminals.add(PushTerminalEnum.C_WORKER_APP);
        if (appSelector.apply(AppTerminalTypeEnum.CMS_WEB_PC))
            terminals.add(PushTerminalEnum.B_ENTERPRISE_APP);
        return terminals;
    }

    private void setOperatorAsSystem(BaseEntityWithOperator<?> entity) {
        entity.setCreatePersonId(0L);
        entity.setCreatePersonName("system");
        entity.setUpdatePersonId(0L);
        entity.setUpdatePersonName("system");
    }

    @Setter
    @Getter
    public static class Param {
        private Set<String> templateCodes;
    }
}