package com.yunzhupaas.base.service.impl;

import cn.hutool.cache.CacheUtil;
import cn.hutool.cache.impl.TimedCache;
import cn.hutool.core.bean.BeanUtil;
import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Lists;
import com.yunzhupaas.base.Pagination;
import com.yunzhupaas.base.entity.BaseLangEntity;
import com.yunzhupaas.base.entity.DictionaryDataEntity;
import com.yunzhupaas.base.mapper.BaseLangMapper;
import com.yunzhupaas.base.model.language.BaseLangForm;
import com.yunzhupaas.base.model.language.BaseLangListVO;
import com.yunzhupaas.base.model.language.BaseLangPage;
import com.yunzhupaas.base.service.BaseLangService;
import com.yunzhupaas.base.service.DictionaryDataService;
import com.yunzhupaas.base.service.SuperServiceImpl;
import com.yunzhupaas.base.vo.PaginationVO;
import com.yunzhupaas.config.ConfigValueUtil;
import com.yunzhupaas.constant.ConfigConst;
import com.yunzhupaas.constant.GlobalConst;
import com.yunzhupaas.constant.MsgCode;
import com.yunzhupaas.consts.ProjectEventConst;
import com.yunzhupaas.event.ProjectEventListener;
import com.yunzhupaas.exception.DataException;
import com.yunzhupaas.i18n.config.I18nProperties;
import com.yunzhupaas.i18n.constant.I18nConst;
import com.yunzhupaas.module.ProjectEventBuilder;
import com.yunzhupaas.module.ProjectEventInstance;
import com.yunzhupaas.util.*;
import lombok.Cleanup;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.collections4.MapUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.io.File;
import java.io.FileInputStream;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReentrantLock;
import java.util.stream.Collectors;

/**
 * 多语言
 *
 * @author 云筑产品开发平台组
 * @version v5.0.0
 * @copyright 深圳市乐程软件有限公司
 * @date 2024/4/28 16:05:49
 */
@Service
public class BaseLangServiceImpl extends SuperServiceImpl<BaseLangMapper, BaseLangEntity> implements BaseLangService {

    @Autowired
    private ConfigValueUtil configValueUtil;

    @Autowired
    private DictionaryDataService dictionaryDataApi;


    @Autowired
    private RedisUtil redisUtil;

    @Autowired
    private I18nProperties i18nProperties;

    // 未使用的情况下, 默认48小时失效, 使用后重新计算缓存时效 租户/语种/前端json
    private TimedCache<String, Map<String, String>> tenantMessageProperties = CacheUtil.newTimedCache(48 * 60 * 60000L);
    // 租户语言加载锁
    private final ConcurrentHashMap<String, ReentrantLock> lockMap = new ConcurrentHashMap<>();

    @Override
    public BaseLangListVO getList(Pagination pagination) {
        List<DictionaryDataEntity> langTypeList = dictionaryDataApi.getListByTypeDataCode(ConfigConst.BASE_LANGUAGE);
        String path = FileUploadUtils.getLocalBasePath() + configValueUtil.getBaseLanguagePath();
        List<Map<String, Object>> tableHead = new ArrayList<>();
        List<Map<String, String>> list = new ArrayList<>();
        List<Map<String, String>> resList = new ArrayList<>();
        tableHead.add(new HashMap() {{
            put("prop", "code");
            put("label", "翻译标记");
        }});
        //不同语言的转换map
        Map<String, LinkedHashMap<String, String>> langKeyMap = new HashMap<>();

        for (DictionaryDataEntity item : langTypeList) {
            tableHead.add(new HashMap() {{
                put("prop", item.getEnCode());
                put("label", item.getFullName());
            }});
            String filePath = path + "java/" + item.getEnCode() + ".json";
            LinkedHashMap<String, String> resMap = new LinkedHashMap<>();
            try {
                File file = new File(filePath);
                @Cleanup FileInputStream fileInputStream = new FileInputStream(file);
                LinkedHashMap<String, Object> jsonObject = JSONObject.parseObject(fileInputStream, LinkedHashMap.class);
                this.parseMap(jsonObject, resMap, null);
            } catch (Exception e) {
                e.printStackTrace();
            }
            langKeyMap.put(item.getEnCode(), resMap);
        }


        for (DictionaryDataEntity item : langTypeList) {
            LinkedHashMap<String, String> eachMap = langKeyMap.get(item.getEnCode());
            for (String key : eachMap.keySet()) {
                Map<String, String> map = list.stream().filter(t -> t.get("code").equals(key)).findFirst().orElse(null);
                if (map != null) {
                    map.put(item.getEnCode(), eachMap.get(key));
                } else {
                    map = new HashMap<>();
                    map.put("code", key);
                    map.put(item.getEnCode(), eachMap.get(key));
                    list.add(map);
                }
            }
        }
        if (StringUtil.isNotEmpty(pagination.getKeyword())) {
            list = list.stream().filter(t -> {
                for (String tkey : t.keySet()) {
                    if (t.get(tkey).contains(pagination.getKeyword())) return true;
                }
                return false;
            }).collect(Collectors.toList());
        }

        //假分页
        if (CollectionUtils.isNotEmpty(list)) {
            List<List<Map<String, String>>> partition = Lists.partition(list, (int) pagination.getPageSize());
            int i = (int) pagination.getCurrentPage() - 1;
            resList = partition.size() > i ? partition.get(i) : Collections.emptyList();
            pagination.setTotal(list.size());
        }

        BaseLangListVO vo = new BaseLangListVO();
        PaginationVO page = JsonUtil.getJsonToBean(pagination, PaginationVO.class);
        vo.setList(resList);
        vo.setTableHead(tableHead);
        vo.setPagination(page);
        return vo;
    }

    /**
     * 多层map，解析成单层key，value
     *
     * @param sourMap
     * @param resMap
     * @param supKey
     */
    public void parseMap(LinkedHashMap<String, Object> sourMap, LinkedHashMap<String, String> resMap, String supKey) {
        for (String key : sourMap.keySet()) {
            if (sourMap.get(key).toString().startsWith("{") && sourMap.get(key).toString().endsWith("}")) {
                LinkedHashMap<String, Object> childSourMap = JSONObject.parseObject(sourMap.get(key).toString(), LinkedHashMap.class);
                String newSupKey = key;
                if (StringUtil.isNotEmpty(supKey)) {
                    newSupKey = supKey + "." + key;
                }
                this.parseMap(childSourMap, resMap, newSupKey);
            } else {
                String value = sourMap.get(key).toString();
                if (supKey != null) {
                    String skey = supKey + "." + key;
                    resMap.put(skey, value);
                } else {
                    String skey = key;
                    resMap.put(skey, value);
                }
            }
        }
    }

    /**
     * 根据不同语种查询列表最大列表
     *
     * @param pagination
     * @param langTypeList
     * @param idList
     * @return
     */
    private IPage<BaseLangEntity> getList(BaseLangPage pagination, List<DictionaryDataEntity> langTypeList, List<String> idList) {
        IPage<BaseLangEntity> listRes = null;
        for (DictionaryDataEntity item : langTypeList) {
            String enCode = item.getEnCode();
            QueryWrapper<BaseLangEntity> queryWrapper = new QueryWrapper<>();
            queryWrapper.lambda().eq(BaseLangEntity::getLanguage, enCode);
            if (CollectionUtils.isNotEmpty(idList)) {
                queryWrapper.lambda().in(BaseLangEntity::getId, idList);
            }
            if (Objects.nonNull(pagination.getType())) {
                queryWrapper.lambda().eq(BaseLangEntity::getType, pagination.getType());
            }
            queryWrapper.lambda().orderByDesc(BaseLangEntity::getCreatorTime);
            Page<BaseLangEntity> page = new Page<>(pagination.getCurrentPage(), pagination.getPageSize());
            IPage<BaseLangEntity> list = this.page(page, queryWrapper);
            if (listRes == null) {
                listRes = list;
            } else {
                if (listRes.getTotal() < list.getTotal()) {
                    listRes = list;
                }
            }
        }
        return listRes;
    }

    @Override
    public BaseLangListVO list(BaseLangPage pagination) {
        List<DictionaryDataEntity> langTypeList = dictionaryDataApi.getListByTypeDataCode(ConfigConst.BASE_LANGUAGE);
        List<Map<String, Object>> tableHead = new ArrayList<>();
        tableHead.add(new HashMap() {{
            put("prop", "code");
            put("label", "翻译标记");
        }});
        for (DictionaryDataEntity item : langTypeList) {
            tableHead.add(new HashMap() {{
                put("prop", item.getEnCode());
                put("label", item.getFullName());
            }});
        }
        String keyword = pagination.getKeyword();
        List<String> idList = new ArrayList<>();
        if (StringUtil.isNotEmpty(keyword)) {
            QueryWrapper<BaseLangEntity> queryWrapper = new QueryWrapper<>();
            queryWrapper.lambda().and(t -> t.like(BaseLangEntity::getEnCode, keyword)
                    .or().like(BaseLangEntity::getFullName, keyword));
            List<BaseLangEntity> list = list(queryWrapper);
            if (CollectionUtils.isEmpty(list)) {
                return new BaseLangListVO(new ArrayList<>(), tableHead, pagination);
            } else {
                idList = list.stream().map(BaseLangEntity::getId).collect(Collectors.toList());
            }
        }
        IPage<BaseLangEntity> list = getList(pagination, langTypeList, idList);
        List<BaseLangEntity> baseLangEntities = pagination.setData(list.getRecords(), list.getTotal());

        List<Map<String, String>> resList = new ArrayList<>();
        for (BaseLangEntity item : baseLangEntities) {
            String groupId = item.getGroupId();
            Map<String, String> resMap = new LinkedHashMap<>();
            List<BaseLangEntity> byGroupId = this.getByGroupId(item.getGroupId());
            resMap.put("id", groupId);
            resMap.put("code", item.getEnCode());
            resMap.put("typeName", Objects.equals(item.getType(), 0) ? "客户端" : "服务端");
            for (BaseLangEntity entity : byGroupId) {
                resMap.put(entity.getLanguage(), entity.getFullName());
            }
            resList.add(resMap);
        }

        BaseLangListVO vo = new BaseLangListVO();
        PaginationVO pagevo = JsonUtil.getJsonToBean(pagination, PaginationVO.class);
        vo.setList(resList);
        vo.setTableHead(tableHead);
        vo.setPagination(pagevo);
        return vo;
    }

    @Override
    public void create(BaseLangForm form) {
        List<BaseLangEntity> byEnode = this.getByEnodeLang(form.getEnCode(), null);
        if (CollectionUtils.isNotEmpty(byEnode)) {
            throw new DataException(MsgCode.SYS051.get());
        }
        Map<String, String> map = form.getMap();
        boolean atLeastOne = false;
        if (MapUtils.isNotEmpty(map)) {
            atLeastOne = map.values().stream().anyMatch(v -> v != null && !v.trim().isEmpty());
        }
        if (!atLeastOne) {
            throw new DataException(MsgCode.SYS052.get());
        }
        String groupId = RandomUtil.uuId();
        for (String key : map.keySet()) {
            BaseLangEntity entity = JsonUtil.getJsonToBean(form, BaseLangEntity.class);
            entity.setGroupId(groupId);
            entity.setLanguage(key);
            entity.setFullName(map.get(key));
            saveEntity(entity);
        }
        this.createReids(form.getType());
    }

    @Override
    public void update(BaseLangForm form) {
        List<BaseLangEntity> langList = getByGroupId(form.getId());
        if (CollectionUtils.isEmpty(langList)) {
            throw new DataException(MsgCode.FA002.get());
        }
        List<BaseLangEntity> byEnodeLang = getByEnodeLang(form.getEnCode(), null);
        List<BaseLangEntity> hasLang = byEnodeLang.stream().filter(t -> !t.getGroupId().equals(form.getId())).collect(Collectors.toList());
        if (CollectionUtils.isNotEmpty(hasLang)) {
            throw new DataException(MsgCode.SYS051.get());
        }

        Map<String, String> map = form.getMap();
        boolean atLeastOne = false;
        if (MapUtils.isNotEmpty(map)) {
            atLeastOne = map.values().stream().anyMatch(v -> v != null && !v.trim().isEmpty());
        }
        if (!atLeastOne) {
            throw new DataException(MsgCode.SYS052.get());
        }
        List<String> keyList = new ArrayList<>(map.keySet());
        String groupId = "";
        List<BaseLangEntity> deleteList = new ArrayList<>();
        for (BaseLangEntity t : langList) {
            if (!keyList.contains(t.getLanguage())) {
                deleteList.add(t);
            }
            groupId = t.getGroupId();
        }
        //删除移除的
        if (CollectionUtils.isNotEmpty(deleteList)) {
            this.removeBatchByIds(deleteList.stream().map(BaseLangEntity::getId).collect(Collectors.toList()));
        }

        for (String key : keyList) {
            BaseLangEntity entity = langList.stream().filter(t -> key.equals(t.getLanguage())).findFirst().orElse(new BaseLangEntity());
            if (StringUtil.isNotEmpty(entity.getId())) {
                entity.setEnCode(form.getEnCode());
                entity.setType(form.getType());
                entity.setFullName(map.get(key));
                entity.setLastModifyTime(DateUtil.getNowDate());
                entity.setLastModifyUserId(UserProvider.getUser().getUserId());
                this.updateById(entity);
            } else {
                entity.setId(RandomUtil.uuId());
                entity.setGroupId(groupId);
                entity.setEnCode(form.getEnCode());
                entity.setType(form.getType());
                entity.setLanguage(key);
                entity.setFullName(map.get(key));
                entity.setCreatorTime(new Date());
                entity.setCreatorUserId(UserProvider.getUser().getUserId());
                this.save(entity);
            }
        }
        this.createReids(form.getType());
    }

    @Override
    public BaseLangForm getInfo(String groupId) {
        List<BaseLangEntity> byEnodeLang = getByGroupId(groupId);
        BaseLangForm form = new BaseLangForm();
        form.setId(groupId);
        LinkedHashMap<String, String> map = new LinkedHashMap<>();
        for (BaseLangEntity entity : byEnodeLang) {
            form.setEnCode(entity.getEnCode());
            form.setType(entity.getType());
            map.put(entity.getLanguage(), entity.getFullName());
        }
        form.setMap(map);
        return form;
    }

    @Override
    public void delete(String groupId) {
        List<BaseLangEntity> byGroupId = getByGroupId(groupId);
        Integer type = byGroupId.get(0).getType();
        QueryWrapper<BaseLangEntity> queryWrapper = new QueryWrapper<>();
        queryWrapper.lambda().eq(BaseLangEntity::getGroupId, groupId);
        this.remove(queryWrapper);
        this.createReids(type);
    }

    @Override
    public void importSaveOrUpdate(List<BaseLangEntity> list) {
        Map<String, String> enCodeGroupId = new HashMap<>();
        boolean hasFront = false;
        boolean hasServer = false;
        for (BaseLangEntity item : list) {
            if (Objects.equals(item.getType(), 0)) {
                hasFront = true;
            }
            if (Objects.equals(item.getType(), 1) || Objects.equals(item.getType(), 2)) {
                hasServer = true;
            }
            List<BaseLangEntity> byEnodeLang = getByEnodeLang(item.getEnCode(), null);
            String groupId = RandomUtil.uuId();
            if (CollectionUtils.isNotEmpty(byEnodeLang)) {
                groupId = byEnodeLang.get(0).getGroupId();
                if (!enCodeGroupId.containsKey(byEnodeLang.get(0).getEnCode())) {
                    enCodeGroupId.put(item.getEnCode(), groupId);
                }
                BaseLangEntity entity = byEnodeLang.stream().filter(t -> t.getLanguage().equals(item.getLanguage())).findFirst().orElse(null);
                if (entity != null) {
                    boolean modify = false;
                    if(!Objects.equals(entity.getType(), item.getType())) {
                        entity.setType(item.getType());
                        modify = true;
                    }
                    if(!Objects.equals(entity.getFullName(), item.getFullName())) {
                        entity.setFullName(item.getFullName());
                        modify = true;
                    }
                    if(modify) {
                        entity.setLastModifyTime(new Date());
                        entity.setLastModifyUserId(UserProvider.getUser().getUserId());
                        this.updateById(entity);
                    }
                } else {
                    entity = JsonUtil.getJsonToBean(item, BaseLangEntity.class);
                    entity.setGroupId(groupId);
                    saveEntity(entity);
                }
            } else {
                if (enCodeGroupId.containsKey(item.getEnCode())) {
                    groupId = enCodeGroupId.get(item.getEnCode());
                } else {
                    enCodeGroupId.put(item.getEnCode(), groupId);
                }
                BaseLangEntity entity = BeanUtil.copyProperties(item, BaseLangEntity.class);
                entity.setGroupId(groupId);
                saveEntity(entity);
            }
        }
        if (hasFront) {
            this.createReids(0);
        }
        if (hasServer) {
            this.createReids(1);
        }
    }

    private void saveEntity(BaseLangEntity entity) {
        entity.setId(RandomUtil.uuId());
        entity.setCreatorTime(new Date());
        entity.setCreatorUserId(UserProvider.getUser().getUserId());
        this.save(entity);
    }

    /**
     * 根据翻译标记和语种获取数据
     *
     * @param enCode
     * @param language
     * @return
     */
    private List<BaseLangEntity> getByEnodeLang(String enCode, String language) {
        QueryWrapper<BaseLangEntity> queryWrapper = new QueryWrapper<>();
        if (StringUtil.isNotEmpty(enCode)) {
            queryWrapper.lambda().eq(BaseLangEntity::getEnCode, enCode);
        }
        if (StringUtil.isNotEmpty(language)) {
            queryWrapper.lambda().eq(BaseLangEntity::getLanguage, language);
        }
        return this.list(queryWrapper);
    }

    /**
     * 根据翻译标记id获取数据
     *
     * @param groupId
     * @return
     */
    private List<BaseLangEntity> getByGroupId(String groupId) {
        QueryWrapper<BaseLangEntity> queryWrapper = new QueryWrapper<>();
        queryWrapper.lambda().eq(BaseLangEntity::getGroupId, groupId);
        queryWrapper.lambda().orderByAsc(BaseLangEntity::getSortCode).orderByAsc(BaseLangEntity::getCreatorTime);
        return this.list(queryWrapper);
    }

    /**
     * 创建修改添加缓存触发
     *
     * @param type
     */
    private void createReids(Integer type) {
        String tenantId = GlobalConst.DEFAULT_TENANT_VALUE;
        if (configValueUtil.isMultiTenancy()) {
            tenantId = TenantHolder.getDatasourceId();
        }
        String cacheType = I18nConst.CACHE_KEY_FRONT;
        if (Objects.equals(type, 1)) {
            cacheType = I18nConst.CACHE_KEY_SERVER;
        }
        String redisKey = cacheType + tenantId;
        PublishEventUtil.publish(new ProjectEventBuilder(redisKey, System.currentTimeMillis()).setMessageModel(ProjectEventConst.EVENT_PUBLISH_MODEL_BROADCASTING));
    }

    /**
     * 组装语种json
     *
     * @param language
     * @return
     */
    private String makeLanguageJson(String language) {
        QueryWrapper<BaseLangEntity> queryWrapper = new QueryWrapper<>();
        queryWrapper.lambda().eq(BaseLangEntity::getLanguage, language);
        queryWrapper.lambda().eq(BaseLangEntity::getType, 0);
        JSONObject json = new JSONObject();
        List<BaseLangEntity> list = list(queryWrapper);
        for (BaseLangEntity baseLangEntity : list) {
//            不需要组装成对象。（组装成对象父级key如果被存储信息了转换json对象有问题）
//            addJsonTree(baseLangEntity.getEnCode(), json, baseLangEntity.getFullName());
            json.put(baseLangEntity.getEnCode(), baseLangEntity.getFullName());
        }
        return json.toJSONString();
    }

    /**
     * 递归添加属性
     *
     * @param enCode
     * @param json
     * @param fullName
     */
    private void addJsonTree(String enCode, JSONObject json, String fullName) {
        if (enCode.contains(".")) {
            String one = enCode.substring(0, enCode.indexOf("."));
            String two = enCode.substring(enCode.indexOf(".") + 1);
            JSONObject towJson = new JSONObject();
            if (json.containsKey(one)) {
                towJson = (JSONObject) json.get(one);
            }
            addJsonTree(two, towJson, fullName);
            json.put(one, towJson);
        } else {
            if (!json.containsKey(enCode)) {
                json.put(enCode, fullName);
            }
        }
    }

    /**
     * 监听缓存修改语种信息
     *
     * @param redisEvent
     */
    @ProjectEventListener(channelRegex = I18nConst.CACHE_KEY_FRONT + ".*")
    public void onRedisKeySetEvent(ProjectEventInstance redisEvent) {
        String key = redisEvent.getChannel();
        // 获取多租户编码
        key = key.substring(I18nConst.CACHE_KEY_FRONT.length());
        tenantMessageProperties.remove(key);
    }

    /**
     * 初始化语种
     *
     * @param tenantId
     * @param locale
     */
    public void loadTenantMessage(String tenantId, Locale locale) {
        String languageJson = this.makeLanguageJson(locale.toLanguageTag());
        Map<String, String> tenantLangJson = tenantMessageProperties.get(tenantId);
        if (tenantLangJson == null) {
            tenantLangJson = new HashMap<>();
            tenantMessageProperties.put(tenantId, tenantLangJson);
        }
        tenantLangJson.put(locale.toLanguageTag(), languageJson);
    }

    /**
     * 前端获取语种json
     *
     * @param locale 语种
     * @return
     */
    @Override
    public String getLanguageJson(Locale locale) {
        // 默认租户或者当前租户
        String tenantId = GlobalConst.DEFAULT_TENANT_VALUE;
        if (configValueUtil.isMultiTenancy()) {
            tenantId = TenantHolder.getDatasourceId();
        }
        // 开启租户未获取到租户 不进行翻译获取
        if (tenantId != null) {
            String language;
            // 租户配置中的语言配置
            String languageTag = locale.toLanguageTag();
            if (!tenantMessageProperties.containsKey(tenantId) || !tenantMessageProperties.get(tenantId).containsKey(languageTag)) {
                // 租户其他线程正在加载多语言则直接返回
                ReentrantLock lock = lockMap.computeIfAbsent(tenantId, k -> new ReentrantLock());
                boolean isLock = false;
                try {
                    // 如果其他线程正在处理租户翻译则最多等待十秒钟
                    isLock = lock.tryLock(5L, TimeUnit.SECONDS);
                    if (isLock) {
                        if (!tenantMessageProperties.containsKey(tenantId) || !tenantMessageProperties.get(tenantId).containsKey(languageTag)) {
                            loadTenantMessage(tenantId, locale);
                        }
                    }
                } catch (InterruptedException e) {
                } finally {
                    if (isLock) {
                        lock.unlock();
                    }
                }
            }
            language = tenantMessageProperties.get(tenantId).get(languageTag);
            // 语言不存在翻译配置, 强制返回默认配置语言, 已经是默认语言无法处理
            if ((language == null || "{}".equals(language)) && !Objects.equals(locale.toLanguageTag(), i18nProperties.getDefaultLanguage())) {
                return getLanguageJson(Locale.forLanguageTag(i18nProperties.getDefaultLanguage()));
            }
            return language;
        }
        return null;
    }

    @Override
    public List<BaseLangEntity> getServerLang(Locale locale) {
        QueryWrapper<BaseLangEntity> queryWrapper = new QueryWrapper<>();
        queryWrapper.lambda().eq(BaseLangEntity::getLanguage, locale.toLanguageTag());
        List<Integer> type = ImmutableList.of(0, 1);
        queryWrapper.lambda().in(BaseLangEntity::getType, type);
        return list(queryWrapper);
    }
}

