package cn.ey.biz.module.system.service.i18n;

import cn.ey.biz.common.pojo.PageResult;
import cn.ey.biz.common.util.collection.CollectionUtils;
import cn.ey.biz.framework.mybatis.core.util.MyBatisUtils;
import cn.ey.biz.module.system.api.i18n.dto.I18nDTO;
import cn.ey.biz.module.system.controller.admin.i18n.vo.*;
import cn.ey.biz.module.system.convert.i18n.I18nConvert;
import cn.ey.biz.module.system.dal.dataobject.i18n.I18nAppDO;
import cn.ey.biz.module.system.dal.dataobject.i18n.I18nDO;
import cn.ey.biz.module.system.dal.mapper.i18n.I18nAppBatchInsertMapper;
import cn.ey.biz.module.system.dal.mapper.i18n.I18nAppMapper;
import cn.ey.biz.module.system.dal.mapper.i18n.I18nBatchInsertMapper;
import cn.ey.biz.module.system.dal.mapper.i18n.I18nMapper;
import cn.ey.biz.module.system.enums.i18n.I18nEnum;
import cn.ey.biz.module.system.enums.i18n.I18nLangEnum;
import cn.ey.biz.module.system.mq.producer.i8n.I18nProducerRedis;
import cn.hutool.core.collection.CollUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Lists;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.extern.slf4j.Slf4j;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.validation.annotation.Validated;

import javax.annotation.PostConstruct;
import javax.annotation.Resource;
import javax.validation.Valid;
import java.util.*;

import static cn.ey.biz.common.exception.util.ServiceExceptionUtil.exception;
import static cn.ey.biz.module.system.enums.ErrorCodeConstants.*;

/**
 * 多语言 Service 实现类
 */
@Slf4j
@Service
@Validated
public class I18nServiceImpl implements I18nService {

    @Resource
    private I18nProducerRedis i18nProducer;
    /**
     * 定时执行
     * 因为已经通过 Redis Pub/Sub 机制，所以频率不需要高
     */
    private static final long SCHEDULER_PERIOD = 5 * 60 * 1000L;
    /**
     * 缓存的最大更新时间，用于后续的增量轮询，判断是否有更新
     */
    private volatile Date maxUpdateTime;
    /**
     * key：语言+key+type
     * <p>
     * 这里声明 volatile 修饰的原因是，每次刷新时，直接修改指向
     */
    private volatile Map<LangKey, I18nDO> i18nCache;

    public String getLabel(String key, String lang, String type) {
        LangKey langKey = new LangKey()
                .setKey(key)
                .setLang(lang)
                .setType(type);
        I18nDO i18nDO = i18nCache.get(langKey);
        if (i18nDO == null) {
            return null;
        } else {
            return i18nDO.getLabel();
        }
    }

    @Resource
    private I18nMapper i18nMapper;

    @Resource
    private I18nBatchInsertMapper i18nBatchInsertMapper;

    @Resource
    private I18nAppBatchInsertMapper i18nAppBatchInsertMapper;

    @Resource
    private I18nAppMapper i18nAppMapper;

    /**
     * 初始化 {@link #i18nCache}
     */
    @Override
    @PostConstruct
    public synchronized void initLocalCache() {
        // 获取语言列表，如果有更新
        List<I18nDO> i18nList = this.loadI18nIfUpdate(maxUpdateTime);
        if (CollUtil.isEmpty(i18nList)) {
            return;
        }

        // 构建缓存
        ImmutableMap.Builder<LangKey, I18nDO> i18nCacheBuilder = ImmutableMap.builder();
        i18nList.forEach(i18nDO -> {
            i18nCacheBuilder.put(new LangKey()
                            .setKey(i18nDO.getKey())
                            .setLang(i18nDO.getLang())
                            .setType(i18nDO.getType()),
                    i18nDO);
        });
        i18nCache = i18nCacheBuilder.build();
        maxUpdateTime = CollectionUtils.getMaxValue(i18nList, I18nDO::getUpdateTime);
        log.info("[initLocalCache][缓存语言，数量为:{}]", i18nList.size());
    }

    @Scheduled(fixedDelay = SCHEDULER_PERIOD, initialDelay = SCHEDULER_PERIOD)
    public void schedulePeriodicRefresh() {
        initLocalCache();
    }

    private List<I18nDO> loadI18nIfUpdate(Date maxUpdateTime) {
        // 第一步，判断是否要更新。
        if (maxUpdateTime == null) { // 如果更新时间为空，说明 DB 一定有新数据
            log.info("[loadI18nIfUpdate][首次加载全量语言]");
        } else { // 判断数据库中是否有更新
            if (i18nMapper.selectMenuCountByUpdateTimeGt(maxUpdateTime) == 0) {
                return null;
            }
            log.info("[loadI18nIfUpdate][增量加载全量语言]");
        }
        // 第二步，如果有更新，则从数据库加载所有
        return i18nMapper.selectList(new I18nExportReqVO().setType("menu"));
    }


    @Override
    public void createI18nList(List<I18nCreateReqVO> createReqVO) {
        List<I18nDO> list = Lists.newArrayList();
        createReqVO.forEach(item -> {
            I18nDO i18n = I18nConvert.INSTANCE.convert(item);
            list.add(i18n);
        });
        i18nBatchInsertMapper.saveBatch(list);
        // 发送刷新消息
        i18nProducer.sendI18nRefreshMessage();
    }

    @Override
    public void importI18nList(@Valid List<I18nDTO> i18ns) {

        List<I18nDO> i18nDOS = new ArrayList<>();
        i18ns.forEach(i18nDTO -> i18nDTO.getLangs().forEach(lang -> {
            I18nDO i18nDO = i18nMapper.selectOne(new LambdaQueryWrapper<I18nDO>()
                    .eq(I18nDO::getKey, i18nDTO.getKey()).eq(I18nDO::getLang, lang.getLang()));
            if (i18nDO == null) {
                i18nDO = new I18nDO()
                        .setType(i18nDTO.getType())
                        .setMemo(i18nDTO.getMemo())
                        .setLang(lang.getLang())
                        .setKey(i18nDTO.getKey())
                        .setLabel(lang.getLabel());
                i18nDOS.add(i18nDO);
            }
        }));
        i18nMapper.insertBatch(i18nDOS);

        List<I18nAppDO> i18nApps = new ArrayList<>();
        i18ns.forEach(i18nDTO -> i18nDTO.getAppIds().forEach(appId -> {
            I18nAppDO i18nAppDO = i18nAppMapper.selectOne(new LambdaQueryWrapper<I18nAppDO>()
                    .eq(I18nAppDO::getApp, appId).eq(I18nAppDO::getI18nKey, i18nDTO.getKey()));
            if (i18nAppDO == null) {
                i18nAppDO = new I18nAppDO();
                i18nAppDO.setApp(appId);
                i18nAppDO.setI18nKey(i18nDTO.getKey());
                i18nApps.add(i18nAppDO);
            }
        }));
        i18nAppBatchInsertMapper.saveBatch(i18nApps);
    }

    @Override
    @Transactional
    public Long createI18n(I18nCreateReqVO createReqVO) {
        List<I18nDO> i18nDOS = new ArrayList<>();
        I18nDO i18n = I18nConvert.INSTANCE.convert(createReqVO);
        List<String> list = Lists.newArrayList();
        for (I18nRespVO keys : createReqVO.getLangs()) {
            if (list.contains(keys.getLang() + keys.getKey())) {
                throw exception(I18N_LEAST_LANG_NOT_REPETITION, keys.getLang());
            } else {
                list.add(keys.getLang() + keys.getKey());
            }
            I18nDO i18nDO = new I18nDO()
                    .setType(I18nEnum.LABEL.getType())
                    .setMemo(createReqVO.getMemo())
                    .setLang(keys.getLang())
                    .setKey(createReqVO.getKey())
                    .setLabel(keys.getLabel());
            i18nDOS.add(i18nDO);
        }
        i18nBatchInsertMapper.saveBatch(i18nDOS);

        List<I18nAppDO> i18nApps = new ArrayList<>();
        for (String i18nApp : createReqVO.getAppIds()) {
            I18nAppDO i18nAppDO = new I18nAppDO();
            i18nAppDO.setApp(i18nApp);
            i18nAppDO.setI18nKey(createReqVO.getKey());
            i18nApps.add(i18nAppDO);
        }
        i18nAppBatchInsertMapper.saveBatch(i18nApps);

        // 发送刷新消息
        i18nProducer.sendI18nRefreshMessage();
        return i18n.getId();
    }

    @Override
    @Transactional
    public void updateI18n(I18nUpdateReqVO updateReqVO) {
        ArrayList<I18nDO> i18nDOS = new ArrayList<>();
        LambdaQueryWrapper<I18nDO> wrapper = Wrappers.<I18nDO>lambdaQuery()
                .eq(I18nDO::getKey, updateReqVO.getOldKey());
        i18nMapper.delete(wrapper);

        for (I18nRespVO keys : updateReqVO.getLangs()) {
            I18nDO i18nDO = new I18nDO()
                    .setType(I18nEnum.LABEL.getType())
                    .setMemo(updateReqVO.getMemo())
                    .setLang(keys.getLang())
                    .setKey(updateReqVO.getKey())
                    .setLabel(keys.getLabel());
            i18nDOS.add(i18nDO);
        }
        i18nBatchInsertMapper.saveBatch(i18nDOS);

        // 删除
        LambdaUpdateWrapper<I18nAppDO> deleteWrapper = Wrappers.<I18nAppDO>lambdaUpdate()
                .eq(I18nAppDO::getI18nKey, updateReqVO.getKey());
        i18nAppMapper.delete(deleteWrapper);

        // 添加
        List<I18nAppDO> i18nApps = new ArrayList<>();
        for (String i18nApp : updateReqVO.getAppIds()) {
            I18nAppDO i18nAppDO = new I18nAppDO();
            i18nAppDO.setApp(i18nApp);
            i18nAppDO.setI18nKey(updateReqVO.getKey());
            i18nApps.add(i18nAppDO);
        }
        i18nAppBatchInsertMapper.saveBatch(i18nApps);

        // 发送刷新消息
        i18nProducer.sendI18nRefreshMessage();
    }

    @Override
    public void deleteI18n(Long id) {
        // 校验存在
        this.validateI18nExists(id);
        // 删除
        i18nMapper.deleteById(id);
    }

    private void validateI18nExists(Long id) {
        if (i18nMapper.selectById(id) == null) {
            throw exception(I18N_NOT_EXISTS);
        }
    }

    @Override
    public I18nDO getI18n(Long id) {
        return i18nMapper.selectById(id);
    }

    @Override
    public List<I18nDO> getI18nList(Collection<Long> ids) {
        return i18nMapper.selectBatchIds(ids);
    }

    @Override
    public PageResult<I18nRespVO> getI18nPage(I18nPageReqVO pageReqVO) {
        IPage page = MyBatisUtils.buildPage(pageReqVO);
        IPage<I18nRespVO> result = i18nMapper.selectPage(page, pageReqVO);
        return new PageResult<I18nRespVO>(result.getRecords(), result.getTotal());
    }

    @Override
    public List<I18nDO> getI18nList(I18nExportReqVO exportReqVO) {
        return i18nMapper.selectList(exportReqVO);
    }

    @Override
    public void deleteI18nByKey(String key) {
        LambdaQueryWrapper<I18nDO> deleteWrapper = Wrappers.<I18nDO>lambdaQuery()
                .eq(I18nDO::getKey, key)
                .eq(I18nDO::getType, I18nEnum.LABEL.getType());
        // 删除
        i18nMapper.delete(deleteWrapper);
        // 删除关联表中数据
        LambdaQueryWrapper<I18nAppDO> deleteAppWrapper = Wrappers.<I18nAppDO>lambdaQuery()
                .eq(I18nAppDO::getI18nKey, key);
        i18nAppMapper.delete(deleteAppWrapper);

        // 发送刷新消息
        i18nProducer.sendI18nRefreshMessage();
    }

    @Override
    public List<I18nDO> getI18nByApp(String acceptLanguage, String app) {
        if (app == null) {
            throw exception(I18N_LEAST_ARGUMENTS_NOT_NULL);
        }
        return i18nMapper.getI18nByApp(acceptLanguage, app);
    }

    @Override
    public String getLabelFromDB(String key, String lang, String type) {
        I18nDO doo = i18nMapper.selectOne(new LambdaQueryWrapper<I18nDO>()
                .eq(I18nDO::getKey, key).eq(I18nDO::getLang, lang)
                .eq(I18nDO::getType, type));
        if (doo != null) {
            return doo.getLabel();
        } else {
            return null;
        }

    }

    @Override
    @Transactional
    public void importI18nListLabel(@Valid List<I18nExcelImportVO> i18ns) {
        ArrayList<I18nDO> i18nDOS = new ArrayList<>();
        List<I18nAppDO> i18nApps = new ArrayList<>();
        for (I18nExcelImportVO  ns:i18ns) {
            I18nDO i18nDOEn = new I18nDO();
            i18nDOEn.setType(I18nEnum.LABEL.getType());
            i18nDOEn.setKey(ns.getKey());
            i18nDOEn.setLang(I18nLangEnum.EN_US.getType());
            i18nDOEn.setLabel(ns.getEn());
            i18nDOS.add(i18nDOEn);
            I18nDO i18nDOZh  = new I18nDO();
            i18nDOZh.setType(I18nEnum.LABEL.getType());
            i18nDOZh.setKey(ns.getKey());
            i18nDOZh.setLang(I18nLangEnum.ZH_CN.getType());
            i18nDOZh.setLabel(ns.getZh());
            i18nDOS.add(i18nDOZh);

            I18nAppDO i18nAppDO=new I18nAppDO();
            i18nAppDO.setI18nKey(ns.getKey());
            i18nAppDO.setApp(ns.getApp());
            i18nApps.add(i18nAppDO);

            LambdaQueryWrapper<I18nDO> wrapper = Wrappers.<I18nDO>lambdaQuery()
                    .eq(I18nDO::getKey, ns.getKey())
                    .eq(I18nDO::getType,I18nEnum.LABEL.getType());
            i18nMapper.delete(wrapper);
            LambdaQueryWrapper<I18nAppDO> deleteAppWrapper = Wrappers.<I18nAppDO>lambdaQuery()
                    .eq(I18nAppDO::getI18nKey,  ns.getKey())
                    .eq(I18nAppDO::getApp,  ns.getApp());
            i18nAppMapper.delete(deleteAppWrapper);
        }
        i18nMapper.insertBatch(i18nDOS);
        i18nAppBatchInsertMapper.saveBatch(i18nApps);
    }

    @Data
    @EqualsAndHashCode
    static class LangKey {
        private String type;
        private String lang;
        private String key;
    }

}
