package com.xjrsoft.module.form.utils;

import cn.hutool.core.convert.Convert;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.db.Entity;
import cn.hutool.extra.spring.SpringUtil;
import cn.hutool.json.JSONObject;
import com.baomidou.mybatisplus.core.toolkit.CollectionUtils;
import com.baomidou.mybatisplus.core.toolkit.StringPool;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.fasterxml.jackson.core.type.TypeReference;
import com.xjrsoft.common.constant.GlobalConstant;
import com.xjrsoft.common.model.generator.ComponentConfig;
import com.xjrsoft.common.utils.LocalDateTimeUtil;
import com.xjrsoft.common.utils.RedisUtil;
import com.xjrsoft.module.form.entity.FormDesignConfig;
import com.xjrsoft.module.generator.constant.ComponentTypeConstant;
import com.xjrsoft.module.generator.utils.GeneratorUtil;
import com.xjrsoft.module.magicapi.service.IMagicApiService;
import com.xjrsoft.module.organization.entity.Department;
import com.xjrsoft.module.organization.entity.User;
import com.xjrsoft.module.system.entity.Area;
import com.xjrsoft.module.system.entity.DictionaryDetail;
import com.xjrsoft.module.system.service.IAreaService;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections.MapUtils;
import org.apache.commons.lang.math.NumberUtils;
import org.ssssssss.magicapi.modules.db.model.PageResult;

import java.sql.Timestamp;
import java.util.*;
import java.util.stream.Collectors;

@Slf4j
public final class FormDataTransUtil {
    private FormDataTransUtil(){}

    private static final RedisUtil redisUtil;

    private static final IMagicApiService magicApiService;

    private static final IAreaService areaService;

    static {
        redisUtil = SpringUtil.getBean(RedisUtil.class);
        magicApiService = SpringUtil.getBean(IMagicApiService.class);
        areaService = SpringUtil.getBean(IAreaService.class);
    }

    public static void transData(List<Entity> dataList, FormDesignConfig formDesignConfig) {
        if (CollectionUtils.isEmpty(dataList)) {
            return;
        }
        List<DictionaryDetail> detailList = null;
        Map<String, Object> userMap = null;
        Map<String, Object> deptMap = null;
//        List<Map<String, Object>> postList = null;
        Map<String, Object> areaMap = null;
        Map<String, Map<String, Object>> apiDataMap = new HashMap<>();
        List<ComponentConfig> componentConfigList = GeneratorUtil.getFormComponentListWithMain(formDesignConfig.getFormJson().getList());
        Map<String, Map<String, Object>> fieldValuesMap = new HashMap<>(componentConfigList.size());
        Map<String, Map<String, String>> multiFieldMap = new HashMap<>();
        Map<String, String> dateFormatMap = new HashMap<>(4);
        for (ComponentConfig componentConfig : componentConfigList) {
            String type = componentConfig.getType();
            String bindField = componentConfig.getBindField();
            Map<String, Object> options = componentConfig.getOptions();
            Integer infoType = MapUtils.getInteger(options, "infoType");
            boolean isCascade = StrUtil.equalsIgnoreCase(ComponentTypeConstant.CASCADE, type);
            if (StrUtil.equalsIgnoreCase(ComponentTypeConstant.CHECKBOX, type)
                    || StrUtil.equalsIgnoreCase(ComponentTypeConstant.SELECT, type)
                    || StrUtil.equalsIgnoreCase(ComponentTypeConstant.ASSOCIATE_SELECT, type)
                    || StrUtil.equalsIgnoreCase(ComponentTypeConstant.ASSOCIATE_POPUP, type)
                    || StrUtil.equalsIgnoreCase(ComponentTypeConstant.MULTIPLE_POPUP, type)
                    || StrUtil.equalsIgnoreCase(ComponentTypeConstant.RADIO, type)
                    || isCascade) {
                String datasourceType = MapUtils.getString(options, "datasourceType");
                if (StrUtil.equalsIgnoreCase(datasourceType, "dic")) {
                    if (detailList == null) {
                        detailList = redisUtil.get(GlobalConstant.DIC_DETAIL_CACHE_KEY, new TypeReference<List<DictionaryDetail>>() {});
                    }
                    Map<String, Object> detailMap = detailList.stream().filter(x -> StrUtil.equalsIgnoreCase(x.getItemId().toString(), MapUtils.getString(options, "itemId"))).collect(Collectors.toMap(DictionaryDetail::getValue, DictionaryDetail::getName, (e1, e2) -> e1));
                    fieldValuesMap.put(bindField, detailMap);
                }else if(StrUtil.equalsIgnoreCase(ComponentTypeConstant.SELECT, type) && StrUtil.equalsIgnoreCase(datasourceType, "staticData")){ // 处理下拉静态数据
                    List<JSONObject> staticOptions = Convert.toList(JSONObject.class, options.get("staticOptions"));
                    Map<String, Object> dataMap = staticOptions.stream().collect(Collectors.toMap(data -> data.get("value").toString(), data -> data.get("label"), (e1, e2) -> e1));
                    fieldValuesMap.put(bindField, dataMap);
                }else if (StrUtil.equalsIgnoreCase(datasourceType, "api") || isCascade) {
                    String apiId = MapUtils.getString(MapUtils.getMap(options, "apiConfig"), "apiId");
                    if (apiDataMap.get(apiId) == null) {
                        Object resultApiData = magicApiService.executeApi(apiId);
                        List<Map<String, Object>> apiDataList = null;
                        if (resultApiData instanceof PageResult) {
                            apiDataList = (List<Map<String, Object>>) ((PageResult) resultApiData).getList();
                        } else if (resultApiData instanceof List) {
                            apiDataList = (List<Map<String, Object>>) resultApiData;
                        }
                        if (isCascade) apiDataList = treeToList(apiDataList);
                        if (apiDataList != null ) apiDataMap.put(apiId, apiDataList.stream().filter(x -> ObjectUtil.isNotNull(x.get("value"))).collect(Collectors.toMap(data -> data.get("value").toString(), data -> data.get("label"), (e1, e2) -> e1)));
                    }
                    fieldValuesMap.put(bindField, apiDataMap.get(apiId));
                }
                if (StrUtil.equalsIgnoreCase(ComponentTypeConstant.MULTIPLE_POPUP, type)
                        || StrUtil.equalsIgnoreCase(ComponentTypeConstant.CHECKBOX, type)
                        || isCascade) {
                    Map<String, String> multiSettingMap = new HashMap<>(4);
                    multiSettingMap.put("separator", MapUtils.getString(options, "separator", StringPool.COMMA));
                    multiSettingMap.put("showFormat", MapUtils.getString(options, "showFormat"));
                    multiFieldMap.put(bindField, multiSettingMap);
                }
            } else if (StrUtil.equalsIgnoreCase(ComponentTypeConstant.AREA, type)) {
                Set<Long> areaValueList = new LinkedHashSet<>();
                for (Map<String, Object> data : dataList) {
                    String values = MapUtils.getString(data, bindField);
                    if (StrUtil.isEmpty(values)) continue;
                    String[] valueArray = values.split(StringPool.COMMA);
                    areaValueList.addAll(Arrays.stream(valueArray).map(NumberUtils::toLong).collect(Collectors.toSet()));
                }
                if (CollectionUtils.isNotEmpty(areaValueList)) {
                    List<Area> areaList = areaService.list(Wrappers.<Area>query().lambda().in(Area::getId, areaValueList));
                    areaMap = areaList.stream().collect(Collectors.toMap(area -> area.getId().toString(), Area::getName, (e1, e2) -> e1));
                } else {
                    areaMap = new HashMap<>(0);
                }
                fieldValuesMap.put(bindField, areaMap);
                multiFieldMap.put(bindField, null);
            }else if (StrUtil.equalsIgnoreCase(ComponentTypeConstant.ORGANIZATION, type)
                    || (StrUtil.equalsIgnoreCase(ComponentTypeConstant.INFO, type) && infoType.equals(1))) {
                if (deptMap == null) {
                    List<Department> departmentList = redisUtil.get(GlobalConstant.DEP_CACHE_KEY, new TypeReference<List<Department>>() {});
                    deptMap = departmentList.stream().collect(Collectors.toMap(department -> department.getId().toString(), Department::getName, (e1, e2) -> e1));
                }
                fieldValuesMap.put(bindField, deptMap);
            } else if (StrUtil.equalsIgnoreCase(ComponentTypeConstant.USER, type)
                    || (StrUtil.equalsIgnoreCase(ComponentTypeConstant.INFO, type) && infoType.equals(0))) {
                if (userMap == null) {
                    List<User> userList = redisUtil.get(GlobalConstant.USER_CACHE_KEY, new TypeReference<List<User>>() {});
                    userMap = userList.stream().collect(Collectors.toMap(user -> user.getId().toString(), User::getName, (e1, e2) -> e1));
                }
                if (infoType == null) {
                    multiFieldMap.put(bindField, null);
                }
                fieldValuesMap.put(bindField, userMap);
            } else if (StrUtil.equalsIgnoreCase(ComponentTypeConstant.DATE, type)) {
                dateFormatMap.put(bindField, LocalDateTimeUtil.convertFontFormat(MapUtils.getString(options, "format")));
            } else if (StrUtil.equalsIgnoreCase(ComponentTypeConstant.DATE_RANGE, type)) {
                String format = LocalDateTimeUtil.convertFontFormat(MapUtils.getString(options, "format"));
                dateFormatMap.put(componentConfig.getBindStartTime(), format);
                dateFormatMap.put(componentConfig.getBindEndTime(), format);
            }
        }

        for (Map<String, Object> data : dataList) {
            for (Map.Entry<String, Map<String, Object>> entry : fieldValuesMap.entrySet()) {
                String key = entry.getKey();
                Object o = data.get(key);
                if (StrUtil.isEmptyIfStr(o)) {
                    continue;
                }
                String value = String.valueOf(o);
                Map<String, Object> entryValue = entry.getValue();
                if (multiFieldMap.containsKey(key)) {
                    List<String> values = StrUtil.split(value, StringPool.COMMA);
                    Map<String, String> multiSettingsMap = multiFieldMap.get(key);
                    StringBuilder showValue = new StringBuilder();
                    for (int i = 0; i < values.size(); i++) {
                        String valueStr = values.get(i);
                        String separator = StringPool.COMMA;
                        Object showValueStr = entryValue.get(valueStr);
                        if (StrUtil.isEmptyIfStr(showValueStr)) continue;
                        if (CollectionUtils.isNotEmpty(multiSettingsMap)) {
                            separator = MapUtils.getString(multiSettingsMap, "separator", StringPool.COMMA);
                            String showFormat = MapUtils.getString(multiSettingsMap, "showFormat");
                            if (StrUtil.equalsIgnoreCase(showFormat, "showMostChildLevel")) {
                                if (i == values.size() - 1) {
                                    showValue.append(showValueStr);
                                }
                                continue;
                            } else {
                                showValue.append(showValueStr);
                            }
                        } else {
                            showValue.append(showValueStr);
                        }
                        if (i < values.size() - 1) {
                            showValue.append(separator);
                        }
                    }
                    if (showValue.length() > 0) data.put(key, showValue.toString());
                } else {
                    Object obj = entryValue.get(value);
                    if (StrUtil.isEmptyIfStr(obj)) continue;
                    data.put(key, obj);
                }
            }
            // 时间格式化处理
            for (Map.Entry<String, String> entry : dateFormatMap.entrySet()) {
                String key = entry.getKey();
                Object value = data.get(key);
                if (value == null) {
                    continue;
                }
                if (value instanceof Timestamp) {
                    Timestamp timestamp = (Timestamp) value;
                    data.put(key, LocalDateTimeUtil.format(timestamp.toLocalDateTime(), entry.getValue()));
                }
            }
        }
    }

    public static void transDataOver(List<Entity> dataList, FormDesignConfig formDesignConfig) {
        if (CollectionUtils.isEmpty(dataList)) {
            return;
        }
        List<DictionaryDetail> detailList = null;
        Map<String, Object> userMap = null;
        Map<String, Object> deptMap = null;
//        List<Map<String, Object>> postList = null;
        Map<String, Object> areaMap = null;
        Map<String, Map<String, Object>> apiDataMap = new HashMap<>();
        List<ComponentConfig> componentConfigList = GeneratorUtil.getFormComponentListWithMain(formDesignConfig.getFormJson().getList());
        Map<String, Map<String, Object>> fieldValuesMap = new HashMap<>(componentConfigList.size());
        Map<String, Map<String, String>> multiFieldMap = new HashMap<>();
        Map<String, String> dateFormatMap = new HashMap<>(4);
        List<String> keyList = new ArrayList<>(dataList.size());
        for (ComponentConfig componentConfig : componentConfigList) {
            String type = componentConfig.getType();
            String bindField = componentConfig.getBindField();
            keyList.add(bindField);
            Map<String, Object> options = componentConfig.getOptions();
            Integer infoType = MapUtils.getInteger(options, "infoType");
            boolean isCascade = StrUtil.equalsIgnoreCase(ComponentTypeConstant.CASCADE, type);
            if (StrUtil.equalsIgnoreCase(ComponentTypeConstant.CHECKBOX, type)
                    || StrUtil.equalsIgnoreCase(ComponentTypeConstant.SELECT, type)
                    || StrUtil.equalsIgnoreCase(ComponentTypeConstant.ASSOCIATE_SELECT, type)
                    || StrUtil.equalsIgnoreCase(ComponentTypeConstant.ASSOCIATE_POPUP, type)
                    || StrUtil.equalsIgnoreCase(ComponentTypeConstant.MULTIPLE_POPUP, type)
                    || StrUtil.equalsIgnoreCase(ComponentTypeConstant.RADIO, type)
                    || isCascade) {
                String datasourceType = MapUtils.getString(options, "datasourceType");
                if (StrUtil.equalsIgnoreCase(datasourceType, "dic")) {
                    if (detailList == null) {
                        detailList = redisUtil.get(GlobalConstant.DIC_DETAIL_CACHE_KEY, new TypeReference<List<DictionaryDetail>>() {});
                    }
                    Map<String, Object> detailMap = detailList.stream().filter(x -> StrUtil.equalsIgnoreCase(x.getItemId().toString(), MapUtils.getString(options, "itemId"))).collect(Collectors.toMap(DictionaryDetail::getValue, DictionaryDetail::getName, (e1, e2) -> e1));
                    fieldValuesMap.put(bindField, detailMap);
                } else if (StrUtil.equalsIgnoreCase(datasourceType, "api") || isCascade) {
                    String apiId = MapUtils.getString(MapUtils.getMap(options, "apiConfig"), "apiId");
                    if (apiDataMap.get(apiId) == null) {
                        Object resultApiData = magicApiService.executeApi(apiId);
                        List<Map<String, Object>> apiDataList = null;
                        if (resultApiData instanceof PageResult) {
                            apiDataList = (List<Map<String, Object>>) ((PageResult) resultApiData).getList();
                        } else if (resultApiData instanceof List) {
                            apiDataList = (List<Map<String, Object>>) resultApiData;
                        }
                        if (isCascade) apiDataList = treeToList(apiDataList);
                        if (apiDataList != null ) apiDataMap.put(apiId, apiDataList.stream().filter(x -> ObjectUtil.isNotNull(x.get("value"))).collect(Collectors.toMap(data -> data.get("value").toString(), data -> data.get("label"), (e1, e2) -> e1)));
                    }
                    fieldValuesMap.put(bindField, apiDataMap.get(apiId));
                }
                if (StrUtil.equalsIgnoreCase(ComponentTypeConstant.MULTIPLE_POPUP, type)
                        || StrUtil.equalsIgnoreCase(ComponentTypeConstant.CHECKBOX, type)
                        || isCascade) {
                    Map<String, String> multiSettingMap = new HashMap<>(4);
                    multiSettingMap.put("separator", MapUtils.getString(options, "separator", StringPool.COMMA));
                    multiSettingMap.put("showFormat", MapUtils.getString(options, "showFormat"));
                    multiFieldMap.put(bindField, multiSettingMap);
                }
            } else if (StrUtil.equalsIgnoreCase(ComponentTypeConstant.AREA, type)) {
                Set<Long> areaValueList = new LinkedHashSet<>();
                for (Map<String, Object> data : dataList) {
                    String values = MapUtils.getString(data, bindField);
                    if (StrUtil.isEmpty(values)) continue;
                    String[] valueArray = values.split(StringPool.COMMA);
                    areaValueList.addAll(Arrays.stream(valueArray).map(NumberUtils::toLong).collect(Collectors.toSet()));
                }
                if (CollectionUtils.isNotEmpty(areaValueList)) {
                    List<Area> areaList = areaService.list(Wrappers.<Area>query().lambda().in(Area::getId, areaValueList));
                    areaMap = areaList.stream().collect(Collectors.toMap(area -> area.getId().toString(), Area::getName, (e1, e2) -> e1));
                } else {
                    areaMap = new HashMap<>(0);
                }
                fieldValuesMap.put(bindField, areaMap);
                multiFieldMap.put(bindField, null);
            }else if (StrUtil.equalsIgnoreCase(ComponentTypeConstant.ORGANIZATION, type)
                    || (StrUtil.equalsIgnoreCase(ComponentTypeConstant.INFO, type) && infoType.equals(1))) {
                if (deptMap == null) {
                    List<Department> departmentList = redisUtil.get(GlobalConstant.DEP_CACHE_KEY, new TypeReference<List<Department>>() {});
                    deptMap = departmentList.stream().collect(Collectors.toMap(department -> department.getId().toString(), Department::getName, (e1, e2) -> e1));
                }
                fieldValuesMap.put(bindField, deptMap);
            } else if (StrUtil.equalsIgnoreCase(ComponentTypeConstant.USER, type)
                    || (StrUtil.equalsIgnoreCase(ComponentTypeConstant.INFO, type) && infoType.equals(0))) {
                if (userMap == null) {
                    List<User> userList = redisUtil.get(GlobalConstant.USER_CACHE_KEY, new TypeReference<List<User>>() {});
                    userMap = userList.stream().collect(Collectors.toMap(user -> user.getId().toString(), User::getName, (e1, e2) -> e1));
                }
                if (infoType == null) {
                    multiFieldMap.put(bindField, null);
                }
                fieldValuesMap.put(bindField, userMap);
            } else if (StrUtil.equalsIgnoreCase(ComponentTypeConstant.DATE, type)) {
                dateFormatMap.put(bindField, LocalDateTimeUtil.convertFontFormat(MapUtils.getString(options, "format")));
            } else if (StrUtil.equalsIgnoreCase(ComponentTypeConstant.DATE_RANGE, type)) {
                String format = LocalDateTimeUtil.convertFontFormat(MapUtils.getString(options, "format"));
                dateFormatMap.put(componentConfig.getBindStartTime(), format);
                dateFormatMap.put(componentConfig.getBindEndTime(), format);
            }
        }

        for (Map<String, Object> data : dataList) {
            for (String key : keyList) {
                String addKey = key + StringPool.UNDERSCORE + "api";
                Object o = data.get(key);
                String value = String.valueOf(o);
                data.put(addKey,value);
            }
            for (Map.Entry<String, Map<String, Object>> entry : fieldValuesMap.entrySet()) {
                String key = entry.getKey();
                Object o = data.get(key);
                if (StrUtil.isEmptyIfStr(o)) {
                    continue;
                }
                String value = String.valueOf(o);
                Map<String, Object> entryValue = entry.getValue();
                if (multiFieldMap.containsKey(key)) {
                    List<String> values = StrUtil.split(value, StringPool.COMMA);
                    Map<String, String> multiSettingsMap = multiFieldMap.get(key);
                    StringBuilder showValue = new StringBuilder();
                    for (int i = 0; i < values.size(); i++) {
                        String valueStr = values.get(i);
                        String separator = StringPool.COMMA;
                        Object showValueStr = entryValue.get(valueStr);
                        if (StrUtil.isEmptyIfStr(showValueStr)) continue;
                        if (CollectionUtils.isNotEmpty(multiSettingsMap)) {
                            separator = MapUtils.getString(multiSettingsMap, "separator", StringPool.COMMA);
                            String showFormat = MapUtils.getString(multiSettingsMap, "showFormat");
                            if (StrUtil.equalsIgnoreCase(showFormat, "showMostChildLevel")) {
                                if (i == values.size() - 1) {
                                    showValue.append(showValueStr);
                                }
                                continue;
                            } else {
                                showValue.append(showValueStr);
                            }
                        } else {
                            showValue.append(showValueStr);
                        }
                        if (i < values.size() - 1) {
                            showValue.append(separator);
                        }
                    }
                    if (showValue.length() > 0) data.put(key, showValue.toString());
                } else {
                    Object obj = entryValue.get(value);
                    if (StrUtil.isEmptyIfStr(obj)) continue;
                    data.put(key, obj);
                }
            }
            // 时间格式化处理
            for (Map.Entry<String, String> entry : dateFormatMap.entrySet()) {
                String key = entry.getKey();
                Object value = data.get(key);
                if (value == null) {
                    continue;
                }
                if (value instanceof Timestamp) {
                    Timestamp timestamp = (Timestamp) value;
                    data.put(key, LocalDateTimeUtil.format(timestamp.toLocalDateTime(), entry.getValue()));
                }
            }
        }
    }

    private static List<Map<String, Object>> treeToList(List<Map<String, Object>> treeDataList) {
        if (CollectionUtils.isEmpty(treeDataList)) {
            return treeDataList;
        }
        List<Map<String, Object>> resultList = new ArrayList<>();
        for (Map<String, Object> data : treeDataList) {
            resultList.add(data);
            Object obj = data.get("children");
            if (obj instanceof List) {
                resultList.addAll(treeToList((List<Map<String, Object>>)obj));
            }
        }
        return resultList;
    }
}
