package cn.axzo.msg.center.message.service.card;

import cn.axzo.framework.jackson.utility.JSON;
import cn.axzo.im.center.api.feign.MessageApi;
import cn.axzo.im.center.api.vo.req.SendTemplateMessageParam;
import cn.axzo.im.center.api.vo.req.UpdateMessageRequest;
import cn.axzo.im.center.api.vo.resp.MessageTaskResp;
import cn.axzo.im.center.api.vo.resp.MessageUpdateResponse;
import cn.axzo.msg.center.common.utils.BizAssertions;
import cn.axzo.msg.center.common.utils.MiscUtils;
import cn.axzo.msg.center.dal.CardDao;
import cn.axzo.msg.center.domain.entity.Card;
import cn.axzo.msg.center.domain.entity.MessageTemplateButtonV3;
import cn.axzo.msg.center.message.domain.dto.TemplateModelV3;
import cn.axzo.msg.center.message.domain.vo.GeneralMessagePushVO;
import cn.axzo.msg.center.message.service.card.broadcast.CardBroadcaster;
import cn.axzo.msg.center.message.service.card.domain.CardGroup;
import cn.axzo.msg.center.message.service.card.domain.CardSendModel;
import cn.axzo.msg.center.message.service.card.log.CardLogger;
import cn.axzo.msg.center.message.service.card.log.CardLoggers;
import cn.axzo.msg.center.nimpush.device.PushDeviceService;
import cn.axzo.msg.center.nimpush.device.PushDeviceSnapshots;
import cn.axzo.msg.center.service.ButtonV3;
import cn.axzo.msg.center.service.dto.PeerPerson;
import cn.axzo.msg.center.service.enums.CardBizState;
import cn.axzo.msg.center.service.enums.CardState;
import cn.axzo.msg.center.service.enums.RouterCategoryEnum;
import cn.axzo.msg.center.service.pending.card.domain.CardButtonStates;
import cn.axzo.msg.center.service.pending.request.CardPresetButtonPressedRequest;
import cn.axzo.msg.center.service.pending.request.CardPresetButtonRequest;
import cn.axzo.msg.center.service.pending.request.CardSendRequest;
import cn.axzo.msg.center.service.pending.request.CardUpdateRequest;
import cn.axzo.msg.center.service.pending.request.CardUpdateStateRequest;
import cn.axzo.msg.center.service.pending.request.ResendMessageRequest;
import cn.axzo.msg.center.service.pending.request.SetActionPerformedRequest;
import cn.axzo.msg.center.service.pending.response.CardSendResponse;
import cn.axzo.msg.center.utils.BatchController;
import cn.axzo.msg.center.utils.RecordCursor;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Component;
import org.springframework.transaction.support.TransactionTemplate;

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.function.Supplier;

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

    private final TransactionTemplate transactionTemplate;
    private final CardSupport cardSupport;
    private final CardDao cardDao;
    private final CardLoggers cardLoggers;
    private final MessageApi messageApi;
    private final PushDeviceService pushDeviceService;
    private final CardParser cardParser;
    private final CardBroadcaster cardBroadcaster;
    private final CardProps cardProps;
    private final ExecutorService executor = new ThreadPoolExecutor(
            5, 15,
            5L, TimeUnit.MINUTES,
            new ArrayBlockingQueue<>(2000));

    public CardSendResponse send(CardSendRequest request) {
        // 校验参数
        BizAssertions.assertNotNull(request.getSender(), "发送人不能为空");
        BizAssertions.assertNotEmpty(request.getReceivers(), "接收人不能为空");
        CardTemplate cardTemplate = cardSupport.parseCardTemplate(cardSupport
                .ensureImChannelPresent(request.getTemplateCode()), request);
        // 主要逻辑
        CardRequestContext<CardSendRequest> requestContext = CardRequestContext.create(request);
        CardSendModel sendModel = new CardSendModel(requestContext, cardTemplate);
        cardSupport.buildCardsForSend(sendModel);
        CardLogger sendLogger = cardLoggers.createLogger(requestContext, sendModel.getCards());
        execTransactional(() -> {
            cardSupport.ensureCardIdempotent(request);
            cardDao.saveBatch(sendModel.getCards());
            sendLogger.reloadAndLogCards("send:enqueue");
        });
        PushDeviceSnapshots deviceSnapshots = pushDeviceService.createDeviceSnapshots();
        BatchController groupExecutor = new BatchController("sendCard", request, executor);
        for (CardGroup group : sendModel.getCardGroups()) {
            groupExecutor.submit(() -> {
                SendTemplateMessageParam imRequest = cardSupport.buildImSendRequest(
                        sendModel, group, deviceSnapshots);
                CardLogger groupLogger = cardLoggers.createLogger(requestContext, group.getCards());
                try {
                    MessageTaskResp imResponse = BizAssertions.assertResponse(
                            messageApi.sendTemplateMessageAsync(imRequest));
                    execTransactional(() -> {
                        cardDao.setSendSuccess(group.getCards(), imResponse);
                        groupLogger.reloadAndLogCards("send:success");
                    });
                } catch (Exception e) {
                    log.warn("发送IM消息失败, deleteCardsWhenSendFail={}, request={}",
                            cardProps.isDeleteCardsWhenSendFail(), request, e);
                    groupLogger.reloadAndLogCards("send:fail", e);
                    throw e;
                }
            });
        }
        try {
            groupExecutor.awaitTermination(5, TimeUnit.MINUTES);
        } catch (Exception e) {
            log.warn("发送IM消息失败, deleteCardsWhenSendFail={}, request={}",
                    cardProps.isDeleteCardsWhenSendFail(), request, e);
            execTransactional(() -> {
                if (cardProps.isDeleteCardsWhenSendFail()) {
                    cardSupport.deleteCardIdempotent(request);
                    cardDao.deleteCards(sendModel.getCards());
                    sendLogger.reloadAndLogCards("send:fail:deleteCards");
                }
            });
            throw MiscUtils.wrapException(e);
        }
        CardSendResponse response = new CardSendResponse();
        if (request.isReturnCards()) {
            cardDao.reloadCards(sendModel.getCards()).stream()
                    .map(cardSupport::convertCardInfo)
                    .forEach(response::addCard);
        }
        return response;
    }

    public void updateState(CardUpdateStateRequest request) {
        request.validate();
        TemplateModelV3 templateModel = cardSupport.ensureImChannelPresent(request.getTemplateCode());
        // 不要放到for里面去了
        CardRequestContext<CardUpdateStateRequest> requestContext = CardRequestContext.create(request);
        for (List<Card> cards : cardsCursor(request)) {
            UpdateExecutor executor = new UpdateExecutor(requestContext, "updateState", templateModel, cards);
            executor.update(card -> {
                Card update = executor.createUpdate(card);
                if (request.determineIsCardCompleted())
                    update.setCardState(CardState.COMPLETED);
                update.setBizState(request.getBizState());
            });
        }
    }

    public void setActionPerformed(SetActionPerformedRequest request) {
        TemplateModelV3 templateModel = cardSupport.ensureImChannelPresent(request.getTemplateCode());
        MessageTemplateButtonV3 button = templateModel.findButton(request.getButtonCode()).orElse(null);
        BizAssertions.assertNotNull(button, "找不到对应的按钮. buttonCode={}", request.getButtonCode());
        //noinspection DataFlowIssue
        BizAssertions.assertEquals(RouterCategoryEnum.JUMP, button.getCategory(), "只有跳转类型的按钮才能通过接口失效");
        BizAssertions.assertTrue(button.isPerformActionAvailable(),
                "按钮不支持失效, 未启用'仅待处理可见'. buttonCode={}", request.getButtonCode());
        CardRequestContext<SetActionPerformedRequest> requestContext = CardRequestContext.create(request);
        for (List<Card> cards : cardsCursor(request)) {
            UpdateExecutor executor = new UpdateExecutor(requestContext, "setActionPerformed", templateModel, cards);
            executor.update(new SetActionPerformedBuilder(executor, button, request.getCardBizState()));
        }
    }

    void firePresetButtonPressed(CardPresetButtonPressedRequest request) {
        Card requestCard = cardDao.findCardByBizMessageId(request.getBizMessageId()).orElse(null);
        BizAssertions.assertNotNull(requestCard, "找不到对应的卡片, bizMessageId={}", request.getBizMessageId());
        List<Card> cards = cardDao.collectPersonCards(request.getBizMessageId());
        TemplateModelV3 templateModel = cardSupport.ensureImChannelPresent(requestCard.getTemplateCode());
        UpdateStateResult result = firePresetButtonPressedImpl(
                request, CardRequestContext.create(request), templateModel, cards);
        BizAssertions.assertFalse(result == UpdateStateResult.NO_UPDATE,
                "卡片已是终状态, 无法'{}'", request.getPresetButtonType().getDesc());
    }

    private UpdateStateResult firePresetButtonPressedImpl(
            CardPresetButtonRequest request, CardRequestContext<?> requestContext,
            TemplateModelV3 templateModel, List<Card> cards) {
        if (CollectionUtils.isEmpty(cards)) {
            log.info("未找到任何需要更新的卡片, request={}", request);
            return UpdateStateResult.EMPTY_CARDS;
        }
        MessageTemplateButtonV3 button = templateModel.findPresetButton(request.getPresetButtonType()).orElse(null);
        BizAssertions.assertNotNull(button, "找不到对应的预设按钮, request={}", JSON.toJSONString(request));
        UpdateExecutor executor = new UpdateExecutor(requestContext, "presetButtonPressed", templateModel, cards);
        UpdateStateResult result = executor.update(new SetActionPerformedBuilder(
                executor, button, CardBizState.fromPresetButton(request.getPresetButtonType())));
        if (result == UpdateStateResult.UPDATED) {
            cardBroadcaster.firePresetButtonPressed(executor.updatedCards, request);
            executor.updateCardLogger.reloadAndLogCards("presetButtonPressed:mq:success");
        }
        return result;
    }

    public void resend(ResendMessageRequest request) throws Exception {
        TemplateModelV3 templateModel = cardSupport.ensureImChannelPresent(request.getTemplateCode());
        BizAssertions.assertNotNull(templateModel, "找不到对应的模板, templateCode={}", request.getTemplateCode());
        BatchController resendExecutor = new BatchController("resendCard", request, executor);
        for (List<Card> cards : cardsCursor(request)) {
            resendExecutor.submit(() -> {
                if (request.isRebuildContent())
                    rebuildCardContent(templateModel, cards);
                updateMessages(cards);
            });
        }
        resendExecutor.awaitTermination(10, TimeUnit.MINUTES);
    }

    private RecordCursor<Card> cardsCursor(CardUpdateRequest request) {
        final int batchSize = cardProps.getUpdateCardBatchSize();
        return new RecordCursor<>(batchSize, Card::getId, () -> cardDao.lambdaQuery()
                .eq(Card::getAppCode, request.getAppCode())
                .eq(Card::getTemplateCode, request.getTemplateCode())
                .eq(Card::getBizCode, request.getBizCode())
                .eq(StringUtils.isNotBlank(request.getSubBizCode()), Card::getSubBizCode, request.getSubBizCode())
                .nested(CollectionUtils.isNotEmpty(request.getReceivers()), nested -> {
                    for (PeerPerson receiver : request.getReceivers()) {
                        nested.or().eq(Card::getReceiverPersonId, receiver.getPersonId())
                                // 有可能不区分ouId和workspaceId更新人所有ou和workspace的卡片
                                .eq(receiver.getOuId() != null, Card::getReceiverOuId, receiver.getOuId())
                                .eq(receiver.getWorkspaceId() != null, Card::getReceiverWorkspaceId, receiver.getWorkspaceId());
                    }
                }));
    }

    private void rebuildCardContent(TemplateModelV3 templateModel, List<Card> cards) {
        ArrayList<Card> updates = new ArrayList<>();
        for (Card card : cardDao.reloadCards(cards)) {
            Card update = new Card();
            update.setId(card.getId());
            // 每个消息独立解析, 因为每个卡片的状态和按钮可能已经存在差异了
            CardTemplate cardTemplate = cardSupport.parseCardTemplate(templateModel, card);
            BizAssertions.assertTrue(cardTemplate.isUpdatable(),
                    "模板不支持更新, templateCode={}", card.getTemplateCode());
            GeneralMessagePushVO cardContent = cardParser.parseCardContent(cardTemplate, card);
            // 保留原来的title和content, 避免模版变了但缺少bizParam导致数据不完整
            cardContent.setCardTitle(card.getTitle());
            cardContent.setCardContent(card.getContent());
            cardContent.addDebugInfo("bizCode", card.getBizCode());
            cardContent.addDebugInfo("subBizCode", card.getSubBizCode());
            update.setCardContent(cardContent);
            updates.add(update);
        }
        cardDao.updateBatchById(updates);
    }

    private void updateMessages(List<Card> cards) {
        if (CollectionUtils.isEmpty(cards))
            return;
        UpdateMessageRequest imRequest = new UpdateMessageRequest();
        for (Card card : cardDao.reloadCards(cards)) {
            if (StringUtils.isBlank(card.getBizMessageId()))
                continue;
            UpdateMessageRequest.Update update = new UpdateMessageRequest.Update();
            update.setBizMessageId(card.getBizMessageId());
            Object cardContent = card.getCardContent();
            update.setMsgTemplateContent(cardContent instanceof String ?
                    (String) cardContent : JSON.toJSONString(cardContent));
            imRequest.addUpdate(update);
        }
        MessageUpdateResponse response = BizAssertions.assertResponse(messageApi.updateMessage(imRequest));
        log.info("更新IM消息, request={}, response={}", imRequest, response);
    }

    private void execTransactional(Runnable runnable) {
        transactionTemplate.executeWithoutResult(unused -> runnable.run());
    }

    private <T> T execTransactional(Supplier<T> runnable) {
        return transactionTemplate.execute(status -> runnable.get());
    }

    private class UpdateExecutor {

        final CardLogger updateCardLogger;
        final TemplateModelV3 templateModel;
        final String operation;
        final List<Card> cards;
        final List<Card> updatedCards = new ArrayList<>();
        final List<Card> updates = new ArrayList<>();

        UpdateExecutor(CardRequestContext<?> requestContext, String operation,
                       TemplateModelV3 templateModel, List<Card> cards) {
            this.updateCardLogger = cardLoggers.createLogger(requestContext);
            this.templateModel = templateModel;
            this.operation = operation;
            this.cards = cards;
        }

        UpdateStateResult update(UpdateBuilder builder) {
            if (CollectionUtils.isEmpty(cards))
                return UpdateStateResult.EMPTY_CARDS;
            try {
                return execTransactional(() -> {
                    for (Card card : cardDao.reloadCardsForUpdate(cards))
                        builder.buildUpdate(card);
                    if (CollectionUtils.isEmpty(updates))
                        return UpdateStateResult.NO_UPDATE;
                    cardDao.updateBatchById(updates);
                    CardManager.this.rebuildCardContent(templateModel, updatedCards);
                    updateCardLogger.reloadAndLogCards(String.format("%s:enqueue", operation));
                    CardManager.this.updateMessages(updatedCards);
                    updateCardLogger.reloadAndLogCards(String.format("%s:updateMessage:success", operation));
                    return UpdateStateResult.UPDATED;
                });
            } catch (Exception e) {
                updateCardLogger.reloadAndLogCards(String.format("%s:fail", operation), e);
                throw e;
            }
        }

        Card createUpdate(Card card) {
            updatedCards.add(card);
            updateCardLogger.addCard(card);
            Card update = new Card();
            update.setId(card.getId());
            updates.add(update);
            return update;
        }

    }

    private enum UpdateStateResult {
        EMPTY_CARDS, UPDATED, NO_UPDATE
    }

    private interface UpdateBuilder {
        void buildUpdate(Card card);
    }

    @RequiredArgsConstructor
    private static class SetActionPerformedBuilder implements UpdateBuilder {

        final UpdateExecutor executor;
        final ButtonV3 button;
        final CardBizState bizState;

        @Override
        public void buildUpdate(Card card) {
            if (card.getCardState() == CardState.COMPLETED)
                return;
            Card update = executor.createUpdate(card);
            CardButtonStates buttonStates = CardButtonStates.create(card.getButtonStates());
            buttonStates.setActionPerformed(button);
            update.setButtonStates(buttonStates.getStates());
            update.setCardState(CardState.COMPLETED);
            update.setBizState(bizState);
        }

    }

}