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

import cn.axzo.framework.rocketmq.Event;
import cn.axzo.msg.center.api.mq.CardPresetButtonPressedMessage;
import cn.axzo.msg.center.api.mq.TodoUpdateMessage;
import cn.axzo.msg.center.dal.MessageTemplateV3Dao;
import cn.axzo.msg.center.dal.TodoBusinessDao;
import cn.axzo.msg.center.dal.TodoDao;
import cn.axzo.msg.center.domain.entity.MessageTemplateV3;
import cn.axzo.msg.center.domain.entity.Todo;
import cn.axzo.msg.center.domain.entity.TodoBusiness;
import cn.axzo.msg.center.message.service.card.CardExtInfo;
import cn.axzo.msg.center.message.service.card.CardManager;
import cn.axzo.msg.center.message.service.card.exception.CardIdempotentException;
import cn.axzo.msg.center.message.service.todo.manage.TodoLogger;
import cn.axzo.msg.center.message.service.todo.manage.TodoManager;
import cn.axzo.msg.center.message.service.todo.manage.TodoRequestContext;
import cn.axzo.msg.center.service.dto.PeerPerson;
import cn.axzo.msg.center.service.enums.BizCategoryEnum;
import cn.axzo.msg.center.service.enums.BizFinalStateEnum;
import cn.axzo.msg.center.service.enums.CardBizState;
import cn.axzo.msg.center.service.enums.CodeDefinition;
import cn.axzo.msg.center.service.enums.MessageChannel;
import cn.axzo.msg.center.service.enums.PendingMessageStateEnum;
import cn.axzo.msg.center.service.pending.request.CardSendRequest;
import cn.axzo.msg.center.service.pending.request.CardUpdateStateRequest;
import cn.axzo.msg.center.service.pending.request.PresetButtonPressedRequest;
import cn.axzo.msg.center.service.pending.response.CardSendResponse;
import com.alibaba.fastjson.JSONObject;
import com.google.common.collect.Sets;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Component;

import java.util.Optional;

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

    private static final String APP_CODE = "msg-center:todo";
    private static final String TODO_IDENTITY_CODE_BIZ_PARAM_KEY = "todoIdentityCode";
    private static final String TODO_TYPE_BIZ_PARAM_KEY = "todoType";
    private static final String TODO_CATEGORY_BIZ_PARAM_KEY = "todoCategory";

    private final TodoDao todoDao;
    private final TodoBusinessDao todoBusinessDao;
    private final CardManager cardManager;
    private final TodoLogger todoLogger;
    private final MessageTemplateV3Dao messageTemplateV3Dao;
    private final TodoManager todoManager;

    void onMessage(Event event, TodoUpdateMessage message) {
        log.info("received TodoUpdateMessage: {}", message);
        try {
            sync(event, message);
        } catch (Exception e) {
            log.warn("onMessage error, message={}", message, e);
            throw new RuntimeException(e);
        }
    }

    private void sync(Event event, TodoUpdateMessage message) {
        if (!message.isUpdateCard()) {
            log.warn("not update card, message={}", message);
            return;
        }
        Todo todo = todoDao.findTodoByCode(message.getUpdatedTodo().getIdentityCode())
                .orElse(null);
        if (todo == null) {
            log.warn("todo not found, message={}", message);
            return;
        }
        MessageTemplateV3 template = messageTemplateV3Dao
                .findByCode(todo.getTemplateCode())
                .orElse(null);
        if (template == null) {
            log.warn("template not found, message={}", message);
            return;
        }
        if (!template.determineChannels().contains(MessageChannel.IM)) {
            log.warn("template not support IM, message={}", message);
            return;
        }
        TodoBusiness business = todoBusinessDao.getBusinesses(todo).findBusiness(todo).orElse(null);
        if (business == null) {
            log.warn("business not found, message={}", message);
            return;
        }
        if (todo.getState() == PendingMessageStateEnum.HAS_BEEN_SENT
                || todo.getState() == PendingMessageStateEnum.CREATED)
            sendCard(event, business, todo);
        else
            updateCardState(event, business, todo);
    }

    private void sendCard(Event event, TodoBusiness business, Todo todo) {
        CardSendRequest request = new CardSendRequest();
        request.setAppCode(APP_CODE);
        request.setTemplateCode(todo.getTemplateCode());
        request.setBizCode(todo.getBizCode());
        request.setIdempotentCode(todo.getIdentityCode());
        request.setSubBizCode(todo.getSubBizCode());
        Long promoterPersonId = business.getPromoterPersonId();
        if (promoterPersonId == null) promoterPersonId = 0L;
        Long ouId = business.getOuId();
        if (ouId == null) ouId = 0L;
        request.setSender(PeerPerson.create(promoterPersonId, ouId, business.getOrgId()));
        request.setReceivers(Sets.newHashSet(PeerPerson.create(
                todo.getExecutorPersonId(), todo.getOuId(), todo.getOrgId())));
        JSONObject bizParam = todo.bizParam();
        CardExtInfo cardExtInfo = new CardExtInfo(bizParam);
        cardExtInfo.addExtInfo(TODO_IDENTITY_CODE_BIZ_PARAM_KEY, todo.getIdentityCode());
        cardExtInfo.addExtInfo(TODO_TYPE_BIZ_PARAM_KEY, todo.getType().getCode());
        cardExtInfo.addExtInfo(TODO_CATEGORY_BIZ_PARAM_KEY, business.getBizCategory().getCode());
        request.setBizParam(bizParam);
        request.setRouterParam(todo.routerParam());
        request.setReturnCards(true);
        TodoRequestContext ctx = TodoRequestContext
                .create("sendCard", event)
                .addLogContent("sendCardRequest", request);
        try {
            log.info("sendCard: todo={}", todo);
            CardSendResponse response = cardManager.send(request);
            ctx.addLogContent("sendCardResponse", response);
            todoLogger.logTodoUpdated(ctx, todo);
            log.info("sendCard: success, todo={}", todo);
        } catch (CardIdempotentException ignored) {
            log.warn("sendCard: idempotent, todo={}", todo);
        } catch (Exception e) {
            log.warn("sendCard: error, todo={}", todo, e);
            todoLogger.logTodoUpdated(ctx.copy().addLogContent("exception", e), todo);
        }
    }

    private void updateCardState(Event event, TodoBusiness business, Todo todo) {
        CardUpdateStateRequest request = new CardUpdateStateRequest();
        request.setAppCode(APP_CODE);
        request.setTemplateCode(todo.getTemplateCode());
        request.setBizCode(todo.getBizCode());
        request.setSubBizCode(todo.getSubBizCode());
        request.setReceivers(Sets.newHashSet(PeerPerson.create(
                todo.getExecutorPersonId(), todo.getOuId(), todo.getOrgId())));
        if (todo.getState() != PendingMessageStateEnum.PROCESSING) {
            request.setBizState(business.getBizCategory() == BizCategoryEnum.FLOW
                    ? determineCardBizStateForFlowTodo(business)
                    : determineCardBizStateForBizTodo(todo));
        }
        request.setCardCompleted(todo.getState() == PendingMessageStateEnum.COMPLETED);
        TodoRequestContext ctx = TodoRequestContext
                .create("updateCardState", event)
                .addLogContent("updateCardStateRequest", request)
                .addLogContent("todo", todo);
        if (request.isValid()) {
            try {
                cardManager.updateState(request);
                todoLogger.logTodoUpdated(ctx.copy().addLogContent("updateCardStateResult", "success"), todo);
            } catch (Exception e) {
                log.warn("updateCardState: error, todo={}", todo, e);
                todoLogger.logTodoUpdated(ctx.copy().addLogContent("exception", e), todo);
            }
        } else {
            log.info("updateCardState: invalid request, todo={}", todo);
            todoLogger.logTodoUpdated(ctx.copy().addLogContent("updateCardStateResult", "invalid update card state request"), todo);
        }
    }

    void syncCardPresetButtonPressed(CardPresetButtonPressedMessage message) {
        log.info("onCardPresetButtonPressed: {}", message);
        if (!APP_CODE.equals(message.getCardInfo().getAppCode())) {
            log.info("onCardPresetButtonPressed: not msg-center:todo, message={}", message);
            return;
        }
        String identityCode = new CardExtInfo(message.getCardInfo().determineBizParam())
                .getJsonObject().getString(TODO_IDENTITY_CODE_BIZ_PARAM_KEY);
        if (StringUtils.isBlank(identityCode)) {
            log.warn("onCardPresetButtonPressed: identityCode is blank, message={}", message);
            return;
        }
        Todo todo = todoDao.findTodoByCode(identityCode).orElse(null);
        if (todo == null) {
            log.info("onCardPresetButtonPressed: todo not found, message={}", message);
            return;
        }
        PresetButtonPressedRequest request = new PresetButtonPressedRequest();
        request.setIdentityCode(identityCode);
        request.setPresetButtonType(message.getPresetButtonType());
        request.setOperatorId(message.getOperatorId());
        request.setOperatorName(message.getOperatorName());
        TodoRequestContext ctx = TodoRequestContext.create("syncCardState", message);
        try {
            todoManager.firePresetButtonPressed(request, false);
            todoLogger.logTodoUpdated(ctx, todo);
        } catch (Exception e) {
            todoLogger.logTodoUpdated(ctx.copy().addLogContent("exception", e), todo);
        }
    }

    private static CardBizState determineCardBizStateForBizTodo(Todo todo) {
        if (todo.getState() == PendingMessageStateEnum.COMPLETED)
            return CardBizState.COMPLETED;
        if (todo.getState() == PendingMessageStateEnum.PROCESSING)
            return CardBizState.PROCESSING;
        if (todo.getState() == PendingMessageStateEnum.RETRACT)
            return CardBizState.REVOKED;
        return null;
    }

    private static CardBizState determineCardBizStateForFlowTodo(TodoBusiness business) {
        if (business.getBizFinalState() == BizFinalStateEnum.COMPLETED)
            return CardBizState.COMPLETED;
        if (business.getBizFinalState() == BizFinalStateEnum.RETRACT)
            return CardBizState.REVOKED;
        if (business.getBizFinalState() == BizFinalStateEnum.PASSED)
            return CardBizState.AGREED;
        if (business.getBizFinalState() == BizFinalStateEnum.REJECTED)
            return CardBizState.REJECTED;
        if (business.getBizFinalState() == BizFinalStateEnum.ABORTED)
            return CardBizState.ABORTED;
        return null;
    }

    public static Optional<BizCategoryEnum> findTodoBizCategory(JSONObject bizParam) {
        if (bizParam == null)
            return Optional.empty();
        String category = new CardExtInfo(bizParam)
                .getJsonObject().getString(TODO_CATEGORY_BIZ_PARAM_KEY);
        return CodeDefinition.findByCode(BizCategoryEnum.class, category);
    }

    public static Optional<String> findTodoIdentityCode(JSONObject bizParam) {
        if (bizParam == null)
            return Optional.empty();
        String identityCode = new CardExtInfo(bizParam)
                .getJsonObject().getString(TODO_IDENTITY_CODE_BIZ_PARAM_KEY);
        return Optional.ofNullable(identityCode);
    }

}