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.maokai.api.util.Ref;
import cn.axzo.msg.center.common.utils.BizAssertions;
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.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.dto.PeerPerson;
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.CardUpdatePresetButtonRequest;
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.response.CardSendResponse;
import cn.axzo.msg.center.service.pending.response.CardSendResponse.CardInfo;
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.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;

    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);
        execTransactional(() -> {
            cardDao.saveBatch(sendModel.getCards());
            cardLoggers.createLogger(requestContext, sendModel.getCards())
                    .reloadAndLogCards("send:enqueue");
        });
        PushDeviceSnapshots deviceSnapshots = pushDeviceService.createDeviceSnapshots();
        String imSenderAccount = BizAssertions.assertResponse(
                messageApi.findTemplateRobotImAccount(cardTemplate.getTemplateCode()));
        for (CardGroup group : sendModel.getCardGroups()) {
            SendTemplateMessageParam imRequest = cardSupport.buildImSendRequest(
                    sendModel, group, deviceSnapshots, imSenderAccount);
            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消息失败, request={}", request, e);
                execTransactional(() -> {
                    cardDao.deleteCards(sendModel.getCards());
                    groupLogger.reloadAndLogCards("send:fail", e);
                });
                throw e;
            }
        }
        CardSendResponse response = new CardSendResponse();
        if (request.isReturnCards()) {
            cardDao.reloadCards(sendModel.getCards()).stream()
                    .map(CardManager::convertCardInfo)
                    .forEach(response::addCard);
        }
        return response;
    }

    private static CardInfo convertCardInfo(Card card) {
        CardInfo info = new CardInfo();
        info.setId(card.getId());
        info.setIdentityCode(card.getIdentityCode());
        info.setBizMessageId(card.getBizMessageId());
        info.setReceiverPersonId(card.getReceiverPersonId());
        info.setReceiverOuId(card.getReceiverOuId());
        info.setReceiverWorkspaceId(card.getReceiverWorkspaceId());
        info.setReceiverAppType(card.getReceiverAppType());
        info.setState(card.getBizState());
        info.setImTaskId(card.getImTaskId());
        return info;
    }

    public void updateState(CardUpdateStateRequest request) {
        TemplateModelV3 templateModel = cardSupport.ensureImChannelPresent(request.getTemplateCode());
        Supplier<List<Card>> cursor = cardsCursor(request);
        boolean updated = false;
        // 不要放到for里面去了
        CardRequestContext<?> requestContext = CardRequestContext.create(request);
        for (List<Card> cards = cursor.get(); !cards.isEmpty(); cards = cursor.get()) {
            updated = true;
            CardLogger cardLogger = cardLoggers.createLogger(requestContext, cards);
            List<Card> finalCards = cards;
            execTransactional(() -> {
                cardDao.updateStates(finalCards, request.getStateInfo());
                rebuildCardContent(templateModel, finalCards);
                cardLogger.reloadAndLogCards("updateState:enqueue");
            });
            updateMessages("updateState", cardLogger, cards);
        }
        BizAssertions.assertTrue(updated, "未找到任何需要更新的卡片, request={}", request);
    }

    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());
        firePresetButtonPressedImpl(request, templateModel, cards);
    }

    public void firePresetButtonPressed(CardUpdatePresetButtonRequest request) {
        TemplateModelV3 templateModel = cardSupport.ensureImChannelPresent(request.getTemplateCode());
        Supplier<List<Card>> cursor = cardsCursor(request);
        for (List<Card> cards = cursor.get(); !cards.isEmpty(); cards = cursor.get())
            firePresetButtonPressedImpl(request, templateModel, cards);
    }

    private void firePresetButtonPressedImpl(
            CardPresetButtonRequest request, TemplateModelV3 templateModel, List<Card> cards) {
        MessageTemplateButtonV3 button = templateModel.findPresetButton(request.getPresetButtonType()).orElse(null);
        BizAssertions.assertNotNull(button, "找不到对应的预设按钮, request={}", JSON.toJSONString(request));
        ArrayList<Card> updates = new ArrayList<>(cards.size());
        for (Card card : cards) {
            CardButtonStates buttonStates = CardButtonStates.create(card.getButtonStates());
            //noinspection DataFlowIssue
            buttonStates.setActionPerformed(button.getCode());
            Card update = new Card();
            update.setId(card.getId());
            update.setButtonStates(buttonStates.getStates());
            updates.add(update);
        }
        CardLogger cardLogger = cardLoggers.createLogger(CardRequestContext.create(request), cards);
        execTransactional(() -> {
            cardDao.updateBatchById(updates);
            rebuildCardContent(templateModel, cards);
            cardLogger.reloadAndLogCards("presetButtonPressed:enqueue");
        });
        if (updateMessages("presetButtonPressed", cardLogger, cards)) {
            cardBroadcaster.firePresetButtonPressed(
                    cards, request.getOperatorId(), request.getPresetButtonType());
            cardLogger.reloadAndLogCards("presetButtonPressed:mq:success");
        }
    }

    private Supplier<List<Card>> cardsCursor(CardUpdateRequest request) {
        Ref<Long> maxId = Ref.create(0L);
        return () -> {
            List<Card> cards = 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());
                        }
                    })
                    .gt(Card::getId, maxId.get())
                    .orderByAsc(Card::getId)
                    .last("LIMIT " + cardProps.getCardCursorLimit())
                    .list();
            if (!cards.isEmpty())
                maxId.set(cards.get(cards.size() - 1).getId());
            return cards;
        };
    }

    private void rebuildCardContent(TemplateModelV3 templateModel, List<Card> cards) {
        ArrayList<Card> updates = new ArrayList<>(cards.size());
        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());
            update.setCardContent(cardParser.parseCardContent(cardTemplate, card));
            updates.add(update);
        }
        cardDao.updateBatchById(updates);
    }

    private boolean updateMessages(String operation, CardLogger cardLogger, List<Card> cards) {
        UpdateMessageRequest imRequest = new UpdateMessageRequest();
        for (Card card : cardDao.reloadCards(cards)) {
            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);
        }
        try {
            log.info("更新IM消息, operation={}, request={}", operation, imRequest);
            MessageUpdateResponse imResponse = BizAssertions.assertResponse(messageApi.updateMessage(imRequest));
            log.info("更新IM消息, operation={}, request={}, response={}", operation, imRequest, imResponse);
            cardLogger.reloadAndLogCards(String.format("%s:success", operation));
            return true;
        } catch (Exception e) {
            log.warn("更新IM消息失败, operation={}, request={}", operation, imRequest, e);
            cardLogger.reloadAndLogCards(String.format("%s:fail", operation), e);
            return false;
        }
    }

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

}