package com.persagy.proxy.migration.service.Impl;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.http.HttpException;
import cn.hutool.http.HttpUtil;
import cn.hutool.json.JSONUtil;
import com.fasterxml.jackson.databind.node.ArrayNode;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.persagy.dmp.basic.model.QueryCriteria;
import com.persagy.dmp.basic.utils.JsonNodeUtils;
import com.persagy.dmp.common.constant.ResponseCode;
import com.persagy.dmp.common.model.entity.AuditableEntity;
import com.persagy.dmp.common.model.entity.BaseEntity;
import com.persagy.dmp.common.model.response.CommonResult;
import com.persagy.dmp.common.utils.JsonHelper;
import com.persagy.dmp.migrate.client.DigitalMigrateLogFacade;
import com.persagy.dmp.rwd.migrate.entity.SyncData;
import com.persagy.proxy.common.entity.DmpResult;
import com.persagy.proxy.common.entity.InstanceUrlParam;
import com.persagy.proxy.migration.constant.MigrationConstant;
import com.persagy.proxy.migration.model.DataMigrationExcel;
import com.persagy.proxy.migration.model.DataMigrationResponse;
import com.persagy.proxy.migration.model.MigrationInfo;
import com.persagy.proxy.migration.service.IMigrationAbstractService;
import lombok.extern.slf4j.Slf4j;

import java.io.IOException;
import java.lang.reflect.Field;
import java.util.*;
import java.util.stream.Collectors;

/**
 * @author lvxianyun
 * @title: MigrationAbstractServiceImpl
 * @projectName adm-middleware
 * @description: 提供基础的数据操作方法 using guava
 * @date 2021/12/3 10:56
 */
@Slf4j
public class MigrationAbstractServiceImpl<T> implements IMigrationAbstractService<T> {

    /**
     * adm交付数据（A） ∩ 现场数据(B)
     * （A∩B） A
     * （null ∩ B） null
     * （A ∩ null） null
     * 需要进一步对比的数据
     *
     * @param from
     * @param to
     */
    @Override
    public Map<String, Object> doIntersectionGetFrom(Map<String, Object> from, Map<String, Object> to) {
        if(CollUtil.isEmpty(from) || CollUtil.isEmpty(to)){
            return null;
        }
        Set<String> intersection = CollUtil.intersectionDistinct(from.keySet(), to.keySet());
        if(CollUtil.isEmpty(intersection)){
            return null;
        }
        return getValueForMapByKeys(intersection, from);
    }

    /**
     * adm交付数据（A） ∩ 现场数据(B)
     * （A ∩ B） return B 子集
     * （null ∩ B） return null
     * （A ∩ null） return null
     * 需要进一步对比的数据
     *
     * @param from
     * @param to
     */
    @Override
    public Map<String, Object> doIntersectionGetTo(Map<String, Object> from, Map<String, Object> to) {
        if(CollUtil.isEmpty(from) || CollUtil.isEmpty(to)){
            return null;
        }
        Set<String> intersection = CollUtil.intersectionDistinct(from.keySet(), to.keySet());
        if(CollUtil.isEmpty(intersection)){
            return null;
        }
        return getValueForMapByKeys(intersection, to);
    }

    /**
     * adm交付数据(From) 与 现场数据(to) 差集  返回adm数据
     * （From - to） 返回 From 子集
     * （null - to） 返回 null
     * （From - null） 返回 From
     *
     * 新增
     *
     * @param from
     * @param to
     */
    @Override
    public Map<String, Object> doSubtractAdd(Map<String, Object> from, Map<String, Object> to) {
        if(CollUtil.isEmpty(from)){
            return null;
        }
        if(CollUtil.isEmpty(to)){
            return from;
        }
        Set<String> diff = difference(from.keySet(), to.keySet());
        return getValueForMapByKeys(diff, from);
    }

    /**
     * adm交付数据(From) 与 现场数据(to ) 差集
     * （to - From ）返回 to 子集
     * （to - null ）返回 to
     * （null - From ）返回 null
     *
     *  return 删除数据
     *
     * @param from
     * @param to
     */
    @Override
    public Map<String, Object> doSubtractDel(Map<String, Object> from, Map<String, Object> to) {
        if(CollUtil.isEmpty(from)){
            return to;
        }
        if(CollUtil.isEmpty(to)){
            return null;
        }
        Set<String> diff = difference(from.keySet(), to.keySet());
        return getValueForMapByKeys(diff, to);
    }

    /**
     * 筛选出所有不同的元素
     * @param from
     * @param to
     * @return
     */
    private Set<String> difference(Set<String> from, Set<String> to) {
        if(CollUtil.isEmpty(from)){
            return to;
        }
        if(CollUtil.isEmpty(to)){
            return from;
        }
        Set<String> all = CollUtil.newHashSet(from);
        all.addAll(to);
        Set<String> intersection = CollUtil.intersectionDistinct(from, to);
        Collection<String> diff = CollUtil.subtract(all, intersection);
        return CollUtil.isEmpty(diff)?CollUtil.newHashSet():CollUtil.newHashSet(diff);
    }

    /**
     * 拼接sql
     *
     * @param context
     * @param migrationInfo
     * @param urlStart
     * @return
     */
    @Override
    public String commonUrl(InstanceUrlParam context, String urlStart, MigrationInfo migrationInfo) {
        if(StrUtil.isEmpty(urlStart) || StrUtil.isBlank(urlStart)){
            return null;
        }
        StringBuffer url = new StringBuffer(urlStart);
        url.append("groupCode=").append(migrationInfo.getTargetGroupCode());
        url.append("&projectId=").append(migrationInfo.getTargetProjectId());
        if(StrUtil.isNotEmpty(context.getUserId())){
            url.append("&userId=").append(context.getUserId());
        }
        if(StrUtil.isNotEmpty(context.getAppId())) {
            url.append("&appId=").append(context.getAppId());
        }
        return url.toString();
    }

    /**
     * 向项目侧提交迁移数据
     *
     * @param url
     * @param requestBody
     */
    @Override
    public String commitDataByHttp(String url, String requestBody) {
        try{
            return HttpUtil.post(url, requestBody, 5000);
        }catch (HttpException e){
           log.error(ResponseCode.C0001.getDesc()+",url:"+ url);
           return "error:"+ResponseCode.C0001.getDesc()+",url:"+ url;
        }
    }

    /**
     * 向项目侧查询数据数量
     *
     * @param url
     * @param queryCriteria
     */
    @Override
    public Long queryCountByHttp(String url, QueryCriteria queryCriteria) {
        String body = "";
        try {
            if(queryCriteria != null){
                body = JsonHelper.toJsonStr(queryCriteria);
            }
            String count = HttpUtil.post(url, body, 3000);
            CommonResult commonResult = JSONUtil.toBean(count, CommonResult.class);
            if(commonResult.getResult().equals(DmpResult.SUCCESS)){
                return commonResult.getCount();
            }else{
                log.error(commonResult.getMessage());
                return -1L;
            }
        } catch (IOException e) {
            log.error(ResponseCode.C0001.toString());
            return -1L;
        }
    }

    /**
     * 将list根据自定义规则转为map  id为key,
     * 使用 MigrationConstant.spliter 作为拼接符
     *
     * @param list
     * @return
     */
    @Override
    public Map<String, Object> toEntityMap(List<ObjectNode> list, Class<T> clazz) {
        if(CollUtil.isEmpty(list)){
            return null;
        }
        Map<String, Field> fieldMap = JsonNodeUtils.getFieldMap(clazz);
        Set<String> fields = fieldMap.keySet();
        Map<String, Object> map = new HashMap<>(list.size());
        for(ObjectNode objectNode : list){
            String key = objectNode.get("id").toString();
            StringBuffer value = new StringBuffer();
            for(String fieldKey : fields){
                if(fieldKey.equals(AuditableEntity.DO_PROP_CREATIONTIME)
                        || fieldKey.equals(AuditableEntity.PROP_CREATOR)
                        || fieldKey.equals(AuditableEntity.DO_PROP_MODIFIEDTIME)
                        || fieldKey.equals(AuditableEntity.PROP_MODIFIER)
                        || fieldKey.equals(BaseEntity.PROP_TS)
                        || fieldKey.equals("createApp")
                        || fieldKey.equals("updateApp")
                        || fieldKey.equals(BaseEntity.PROP_TS)
                        || fieldKey.equals("dataSource")
                ){
                    continue;
                }
                value.append(objectNode.get(fieldKey) == null ? "" : objectNode.get(fieldKey)).append(MigrationConstant.SPLITER);
            }
            String valueTemp = value.toString().replaceAll("\"","");
           /* valueTemp = StrUtil.removeAll(valueTemp,"[");
            valueTemp = StrUtil.removeAll(valueTemp,"]");
            valueTemp = StrUtil.removeAll(valueTemp,"},{");
            valueTemp = StrUtil.removeAll(valueTemp,"{");*/
            map.put(key.replaceAll("\"",""), valueTemp);
        }
        return map;
    }


    /**
     * 获取项目现场交付的数据，默认全部
     *
     * @param queryCriteria
     * @param clazz
     * @return
     */
    @Override
    public List getProjectData(QueryCriteria queryCriteria, Class<T> clazz, String url) {
        return processProjectData(null, queryCriteria, clazz, url).getData();
    }

    @Override
    public DataMigrationResponse updateBatch(List vos, Class<T> clazz, String url) {
        return saveOrUpdateCommProcess(vos, clazz, url);
    }

    @Override
    public DataMigrationResponse insertBatch(List vos,  Class<T> clazz, String url) {
       return saveOrUpdateCommProcess(vos, clazz, url);
    }

    /**
     * 批量更新，分批次更新，大于500条，每500条提交一次
     * @param vos
     * @param clazz
     * @param url
     * @return
     */
    private DataMigrationResponse saveOrUpdateCommProcess(List vos,  Class<T> clazz, String url){
        DataMigrationResponse dataMigrationResponse = DataMigrationResponse.success();
        if(vos.size() <= MigrationConstant.BATCH_SUBMIT_DATA_COUNT){
            dataMigrationResponse = processProjectData(vos,null, clazz, url);
        }else{
            int count= vos.size() / MigrationConstant.BATCH_SUBMIT_DATA_COUNT;
            for(int k = 0; k< count + 1 ;k++){
                int end = (k+1)  * MigrationConstant.BATCH_SUBMIT_DATA_COUNT - 1;
                if(end > vos.size()){
                    end = vos.size();
                }
                List admDataSub = vos.subList(k * MigrationConstant.BATCH_SUBMIT_DATA_COUNT, end);
                DataMigrationResponse responseTemp = processProjectData(admDataSub,null, clazz, url);
                if(CollUtil.isNotEmpty(responseTemp.getData())){
                    dataMigrationResponse.addData(responseTemp.getData());
                }
                if(CollUtil.isNotEmpty(responseTemp.getErrorMsg())){
                    dataMigrationResponse.addError(responseTemp.getErrorMsg());
                }
            }
        }
        return dataMigrationResponse;
    }


    @Override
    public DataMigrationResponse deleteBatch(List<String> ids, Class<T> clazz, String url) {
        String response = null;
        try {
            response = commitDataByHttp(url, JsonHelper.toJsonStr(ids));
        } catch (IOException e) {
            log.error(e.getMessage());
            return DataMigrationResponse.error(e.getMessage());
        }
        CommonResult<List> commonResult = JSONUtil.toBean(response, CommonResult.class);
        if(commonResult.getResult().equals(DmpResult.SUCCESS)){
            return DataMigrationResponse.success(ids);
        }else {
            log.error(commonResult.getMessage());
            return DataMigrationResponse.error(commonResult.getResult());
        }
    }

    /**
     * 对比需要更新的数据
     *
     * @param from
     * @param to
     */
    @Override
    public Map<String, Object> compareData(Map<String, Object> from, Map<String, Object> to) {
        if(CollUtil.isEmpty(from) || CollUtil.isEmpty(to)){
            return null;
        }
        Set<String> formSets = from.entrySet().stream().map(en -> String.join(MigrationConstant.SPLITER_UNION,en.getKey(),en.getValue().toString())).collect(Collectors.toSet());
        Set<String> toSets = to.entrySet().stream().map(en -> String.join(MigrationConstant.SPLITER_UNION,en.getKey(),en.getValue().toString())).collect(Collectors.toSet());

        Set<String> difference = difference(formSets, toSets);
        if(CollUtil.isEmpty(difference)){
            return null;
        }
        Set<String> diff = difference.stream().map(entity -> StrUtil.subBefore(entity, MigrationConstant.SPLITER_UNION, true)).collect(Collectors.toSet());
        Map<String, Object> results = getValueForMapByKeys(diff, from);
        return results;
    }

    @Override
    public List<DataMigrationExcel> addErrorInfo(List<String> errorMsg) {
        if(CollUtil.isEmpty(errorMsg)){
            return Collections.emptyList();
        }
        List<DataMigrationExcel> dataMigrationExcels = new ArrayList<>(errorMsg.size());
        for(String msg : errorMsg){
            DataMigrationExcel dataMigrationExcelError = new DataMigrationExcel();
            dataMigrationExcelError.setId("error");
            dataMigrationExcelError.setName(msg);
            dataMigrationExcels.add(dataMigrationExcelError);
        }
        return dataMigrationExcels;
    }

    @Override
    public List<SyncData> addErrorInfoForLog(List<String> errorMsg, String tableName) {
        if(CollUtil.isEmpty(errorMsg) || StrUtil.isEmpty(tableName)){
            return Collections.emptyList();
        }
        List<SyncData> syncDataList = new ArrayList<>(errorMsg.size());
        for(String msg : errorMsg){
            SyncData syncData = new SyncData();
            syncData.setTableName(tableName);
            syncData.setTargetId("error");
            syncData.setError(msg);
            syncData.setSign(3);//异常
            syncDataList.add(syncData);
        }
        return syncDataList;
    }

    /**
     * 调用中台接口，保存迁移日志
     * 日志量 > MigrationConstant.BATCH_SUBMIT_DATA_COUNT 使用分页插入
     *
     * @param context
     * @param syncDataList
     * @return
     */
    @Override
    public List<SyncData> addSynLog(InstanceUrlParam context, List<SyncData> syncDataList) {
        if(CollUtil.isEmpty(syncDataList)){
            return Collections.emptyList();
        }
        //List<SyncData> syncDatasResult = new ArrayList<>(syncDataList.size());
        // 调用中台新增
        if(syncDataList.size() - MigrationConstant.BATCH_SUBMIT_DATA_COUNT < 0){
            DigitalMigrateLogFacade.create(context.getGroupCode(),context.getProjectId(),context.getAppId(), context.getUserId(), syncDataList);
        }else{
            int count= syncDataList.size() / MigrationConstant.BATCH_SUBMIT_DATA_COUNT;
            for(int k = 0; k < count + 1 ;k++){
                int end = (k+1)  * MigrationConstant.BATCH_SUBMIT_DATA_COUNT - 1;
                if(end > syncDataList.size()){
                    end = syncDataList.size();
                }
                List syncData = syncDataList.subList(k * MigrationConstant.BATCH_SUBMIT_DATA_COUNT, end);
                //syncDatasResult.addAll(DigitalMigrateLogFacade.create(context.getGroupCode(),context.getProjectId(),context.getAppId(), context.getUserId(), syncData));
                DigitalMigrateLogFacade.create(context.getGroupCode(),context.getProjectId(),context.getAppId(), context.getUserId(), syncData);
            }
        }
        return syncDataList;
    }

    public DataMigrationResponse processProjectData(List vos, QueryCriteria queryCriteria, Class<T> clazz, String url){
        //处理rwd 请求返回超时
        String body = null;
        try {
            if(CollUtil.isEmpty(vos) && queryCriteria != null){
                body = JsonHelper.toJsonStr(queryCriteria);
            }else{
                body = JsonHelper.toJsonStr(vos);
            }

        } catch (IOException e) {
            log.error(ResponseCode.C0001.toString());
            return DataMigrationResponse.error(ResponseCode.C0001.toString());
        }
        try{
            String response = commitDataByHttp(url, body);
            if(response.equals("Bad Gateway")){
                return DataMigrationResponse.error("Bad Gateway");
            }
            CommonResult<List> commonResult = JSONUtil.toBean(response, CommonResult.class);
            if(commonResult.getResult().equals(DmpResult.SUCCESS)){
                ArrayNode arrayNode = JsonNodeUtils.toArrayNode(commonResult.getData(), null, null);
                if(arrayNode == null){
                    return DataMigrationResponse.success();
                }
                return DataMigrationResponse.success(JsonNodeUtils.toEntity(arrayNode, clazz, null));
            }else {
                log.error(StrUtil.format("获取中台数据失败{}",commonResult.getMessage()));
                return DataMigrationResponse.error(StrUtil.format("获取中台数据失败{}",commonResult.getMessage()));
            }
        }catch (Exception e){
            log.error(StrUtil.format("获取中台数据异常{}",e.getMessage()));
            return DataMigrationResponse.error(StrUtil.format("获取中台数据异常{}",e.getMessage()));
        }

    }

    @Override
    public Map<String, Object> getValueForMapByKeys(Set<String> sets, Map<String, Object> map){
        if(CollUtil.isEmpty(sets) || CollUtil.isEmpty(map)){
            return  null;
        }
        Map<String, Object> targetsMap = new HashMap<>(sets.size());
        sets.forEach(key -> targetsMap.put(key, map.get(key)));
        return targetsMap;
    }
}
