package cn.iocoder.hake.module.mp.service.menu;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.StrUtil;
import cn.iocoder.hake.module.mp.controller.admin.menu.vo.MpMenuSaveReqVO;
import cn.iocoder.hake.module.mp.convert.menu.MpMenuConvert;
import cn.iocoder.hake.module.mp.dal.dataobject.account.MpAccountDO;
import cn.iocoder.hake.module.mp.dal.dataobject.menu.MpMenuDO;
import cn.iocoder.hake.module.mp.dal.mysql.menu.MpMenuMapper;
import cn.iocoder.hake.module.mp.framework.mp.core.MpServiceFactory;
import cn.iocoder.hake.module.mp.framework.mp.core.util.MpUtils;
import cn.iocoder.hake.module.mp.service.account.MpAccountService;
import cn.iocoder.hake.module.mp.service.message.MpMessageService;
import cn.iocoder.hake.module.mp.service.message.bo.MpMessageSendOutReqBO;
import lombok.extern.slf4j.Slf4j;
import me.chanjar.weixin.common.bean.menu.WxMenu;
import me.chanjar.weixin.common.error.WxErrorException;
import me.chanjar.weixin.mp.api.WxMpService;
import me.chanjar.weixin.mp.bean.message.WxMpXmlOutMessage;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.validation.annotation.Validated;

import javax.annotation.Resource;
import javax.validation.Validator;
import java.util.List;

import static cn.iocoder.hake.framework.common.exception.util.ServiceExceptionUtil.exception;
import static cn.iocoder.hake.module.mp.enums.ErrorCodeConstants.MENU_DELETE_FAIL;
import static cn.iocoder.hake.module.mp.enums.ErrorCodeConstants.MENU_SAVE_FAIL;

/**
 * 公众号菜单 Service 实现类
 *
 * @author hake
 */
@Service
@Validated
@Slf4j
public class MpMenuServiceImpl implements MpMenuService {

    @Resource
    private MpMessageService mpMessageService;
    @Resource
    @Lazy // 延迟加载，避免循环引用报错
    private MpAccountService mpAccountService;

    @Resource
    @Lazy // 延迟加载，避免循环引用报错
    private MpServiceFactory mpServiceFactory;

    @Resource
    private Validator validator;

    @Resource
    private MpMenuMapper mpMenuMapper;

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void saveMenu(MpMenuSaveReqVO createReqVO) {
        MpAccountDO account = mpAccountService.getRequiredAccount(createReqVO.getAccountId());
        WxMpService mpService = mpServiceFactory.getRequiredMpService(createReqVO.getAccountId());

        // 参数校验
        createReqVO.getMenus().forEach(this::validateMenu);

        // 第一步，同步公众号
        WxMenu wxMenu = new WxMenu();
        wxMenu.setButtons(MpMenuConvert.INSTANCE.convert(createReqVO.getMenus()));
        try {
            mpService.getMenuService().menuCreate(wxMenu);
        } catch (WxErrorException e) {
            throw exception(MENU_SAVE_FAIL, e.getError().getErrorMsg());
        }

        // 第二步，存储到数据库
        mpMenuMapper.deleteByAccountId(createReqVO.getAccountId());
        createReqVO.getMenus().forEach(menu -> {
            // 先保存顶级菜单
            MpMenuDO menuDO = createMenu(menu, null, account);
            // 再保存子菜单
            if (CollUtil.isEmpty(menu.getChildren())) {
                return;
            }
            menu.getChildren().forEach(childMenu -> createMenu(childMenu, menuDO, account));
        });
    }

    /**
     * 校验菜单的格式是否正确
     *
     * @param menu 菜单
     */
    private void validateMenu(MpMenuSaveReqVO.Menu menu) {
        MpUtils.validateButton(validator, menu.getType(), menu.getReplyMessageType(), menu);
        // 子菜单
        if (CollUtil.isEmpty(menu.getChildren())) {
            return;
        }
        menu.getChildren().forEach(this::validateMenu);
    }

    /**
     * 创建菜单，并存储到数据库
     *
     * @param wxMenu 菜单信息
     * @param parentMenu 父菜单
     * @param account 公众号账号
     * @return 创建后的菜单
     */
    private MpMenuDO createMenu(MpMenuSaveReqVO.Menu wxMenu, MpMenuDO parentMenu, MpAccountDO account) {
        // 创建菜单
        MpMenuDO menu = CollUtil.isNotEmpty(wxMenu.getChildren())
                ? new MpMenuDO().setName(wxMenu.getName())
                : MpMenuConvert.INSTANCE.convert02(wxMenu);
        // 设置菜单的公众号账号信息
        if (account != null) {
            menu.setAccountId(account.getId()).setAppId(account.getAppId());
        }
        // 设置父编号
        if (parentMenu != null) {
            menu.setParentId(parentMenu.getId());
        } else {
            menu.setParentId(MpMenuDO.ID_ROOT);
        }

        // 插入到数据库
        mpMenuMapper.insert(menu);
        return menu;
    }

    @Override
    public void deleteMenuByAccountId(Long accountId) {
        WxMpService mpService = mpServiceFactory.getRequiredMpService(accountId);
        // 第一步，同步公众号
        try {
            mpService.getMenuService().menuDelete();
        } catch (WxErrorException e) {
            throw exception(MENU_DELETE_FAIL, e.getError().getErrorMsg());
        }

        // 第二步，存储到数据库
        mpMenuMapper.deleteByAccountId(accountId);
    }

    @Override
    public WxMpXmlOutMessage reply(String appId, String key, String openid) {
        // 第一步，获得菜单
        MpMenuDO menu = mpMenuMapper.selectByAppIdAndMenuKey(appId, key);
        if (menu == null) {
            log.error("[reply][appId({}) key({}) 找不到对应的菜单]", appId, key);
            return null;
        }
        // 按钮必须要有消息类型，不然后续无法回复消息
        if (StrUtil.isEmpty(menu.getReplyMessageType())) {
            log.error("[reply][menu({}) 不存在对应的消息类型]", menu);
            return null;
        }

        // 第二步，回复消息
        MpMessageSendOutReqBO sendReqBO = MpMenuConvert.INSTANCE.convert(openid, menu);
        return mpMessageService.sendOutMessage(sendReqBO);
    }

    @Override
    public List<MpMenuDO> getMenuListByAccountId(Long accountId) {
        return mpMenuMapper.selectListByAccountId(accountId);
    }

}
