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

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.StrUtil;
import com.fasterxml.jackson.databind.node.ArrayNode;
import com.fasterxml.jackson.databind.node.JsonNodeFactory;
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.define.entity.ObjectTypeDefine;
import com.persagy.dmp.digital.client.DigitalObjectFacade;
import com.persagy.dmp.digital.entity.ObjectDigital;
import com.persagy.dmp.rwd.migrate.entity.SyncData;
import com.persagy.proxy.adm.request.AdmResponse;
import com.persagy.proxy.common.entity.InstanceUrlParam;
import com.persagy.proxy.migration.constant.MigrationConstant;
import com.persagy.proxy.migration.constant.MigrationObjType;
import com.persagy.proxy.migration.constant.MigrationTable;
import com.persagy.proxy.migration.constant.MigrationType;
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.IDataMigration;
import com.persagy.proxy.migration.service.IDataMigrationProcess;
import com.persagy.proxy.migration.service.Impl.MigrationAbstractServiceImpl;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;

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

/**
 * @author lvxianyun
 * @title: ObjectMigration
 * @projectName adm-middleware
 * @description: 迁移adm交付的数据到现场，覆盖现场数据，提供增加、删除、修改 记录放入excel; dt_object 数据迁移 adm -> target project
 * @date 2021/12/8 19:53
 */
@Slf4j
@Service
public class ObjectDigitalMigration extends MigrationAbstractServiceImpl<ObjectDigital> implements IDataMigration, IDataMigrationProcess<ObjectDigital> {

    /**
     * 数据迁移前，数据对比信息 汇总
     *
     * @param context
     * @param migrationInfo
     * @return List<DataMigrationExcel>
     */
    @Override
    public List<DataMigrationExcel> migrateForExcel(InstanceUrlParam context, MigrationInfo migrationInfo) {
        log.info("######################### dt_object 数据迁移内容获取开始 #########################");
        long start = System.currentTimeMillis();
        List<DataMigrationExcel> dataMigrationExcels = new ArrayList<>();

        for(MigrationObjType objType : MigrationObjType.values()){
            log.info("######################### dt_object "+objType.getCode()+"数据迁移内容获取开始 #########################");

            QueryCriteria queryCriteria = getQueryCriteria(objType.getCode());
            dataMigrationExcels.addAll(startMigrateForExcel(context,migrationInfo,queryCriteria));

            log.info("######################### dt_object "+objType.getCode()+"数据迁移内容获取结束 #########################");
        }

        long end = System.currentTimeMillis();
        log.info("######################### dt_object 数数据迁移内容获取已结束 时间："+(end-start)+" #########################");
        return dataMigrationExcels;
    }

    /**
     * 迁移数据，汇总记录日志
     * @param context
     * @param migrationInfo
     * @return
     */
    @Override
    public AdmResponse migrateForLog(InstanceUrlParam context, MigrationInfo migrationInfo) {
        log.info("######################### dt_object 同步开始 #########################");
        long start = System.currentTimeMillis();
        for(MigrationObjType objType : MigrationObjType.values()){
            log.info("######################### dt_object "+objType.getCode()+"同步开始 #########################");

            QueryCriteria queryCriteria = getQueryCriteria(objType.getCode());
            startMigrateForLog(context,migrationInfo,queryCriteria);

            log.info("######################### dt_object "+objType.getCode()+"同步结束 #########################");
        }
        long end = System.currentTimeMillis();
        log.info("######################### dt_object 数据迁移已结束 时间："+(end-start)+" #########################");
        return AdmResponse.success();
    }

    /**
     * 数据迁移
     *
     * @param context
     * @return sql 文件
     */
    @Override
    public String migrateForSql(InstanceUrlParam context) {
        StringBuffer sqls = new StringBuffer(" \n -- 数据迁移  dt_object ------ \n");
        //获取已经采集的数据
        String sqls_temp = "";
        for(MigrationObjType objType : MigrationObjType.values()){
            QueryCriteria queryCriteria = getQueryCriteria(objType.getCode());
            List<ObjectNode> admData = getAdmData(context,queryCriteria);
            if(CollUtil.isEmpty(admData)){
                continue;
            }
            sqls_temp += appendValues(admData).toString();
        }
        if(sqls_temp.length() == 0){
            return "";
        }
        sqls.append(MigrationConstant.DT_OBJECT_SQL_PRE);
        sqls.append(sqls_temp);
        String sql = sqls.toString();
        sql = sql.substring(0, sql.lastIndexOf(","));
        sql += ";";
        sql = sql.replaceAll("\"","\\\\\"");
        return sql;
    }

    private StringBuffer appendValues(List<ObjectNode> admData){
        StringBuffer sqls = new StringBuffer();
        admData.forEach(obj -> {
            ObjectDigital objectDigital = JsonNodeUtils.toEntity(obj, ObjectDigital.class, "infos");
            sqls.append("(");
            sqls.append("'").append(objectDigital.getId()).append("',");
            if (objectDigital.getName() == null) {
                sqls.append(objectDigital.getName()).append(",");
            } else {
                sqls.append("'").append(objectDigital.getName()).append("',");
            }

            if (objectDigital.getLocalId() == null) {
                sqls.append(objectDigital.getLocalId()).append(",");
            } else {
                sqls.append("'").append(objectDigital.getLocalId()).append("',");
            }
            if (objectDigital.getLocalName() == null) {
                sqls.append(objectDigital.getLocalName()).append(",");
            } else {
                sqls.append("'").append(objectDigital.getLocalName()).append("',");
            }
            sqls.append("'").append(objectDigital.getGroupCode()).append("',");
            sqls.append("'").append(objectDigital.getProjectId()).append("',");
            sqls.append("'").append(objectDigital.getObjType()).append("',");
            sqls.append("'").append(objectDigital.getClassCode()).append("',");
            sqls.append("'").append(objectDigital.getGrouping()).append("',");
            sqls.append("'").append(objectDigital.getInfos()).append("',");
            if (objectDigital.getVirtualCodes() == null) {
                sqls.append(objectDigital.getVirtualCodes()).append(",");
            } else {
                sqls.append("'").append(objectDigital.getVirtualCodes()).append("',");
            }
            if (objectDigital.getCreateApp() == null) {
                sqls.append(objectDigital.getCreateApp()).append(",");
            } else {
                sqls.append("'").append(objectDigital.getCreateApp()).append("',");
            }
            if (objectDigital.getUpdateApp() == null) {
                sqls.append(objectDigital.getUpdateApp()).append(",");
            } else {
                sqls.append("'").append(objectDigital.getUpdateApp()).append("',");
            }
            sqls.append("'").append(objectDigital.getCreator()).append("',");
            sqls.append("'").append(objectDigital.getCreationTime()).append("',");
            sqls.append("'").append(objectDigital.getModifier()).append("',");
            sqls.append("'").append(objectDigital.getModifiedTime()).append("',");
            sqls.append(objectDigital.getValid());
            sqls.append(")\n ,");
        });
        return sqls;
    }
    private QueryCriteria getQueryCriteria(String typeCode){
        QueryCriteria queryCriteria = new QueryCriteria();
        queryCriteria.setSize(-1L);
        ObjectNode criteria = JsonNodeFactory.instance.objectNode();
        criteria.put("valid", 1);
        criteria.put("objType", typeCode);
        queryCriteria.setCriteria(criteria);
        return queryCriteria;
    }

    /**
     * 迁移数据，记录日志
     *
     */
    private void startMigrateForLog(InstanceUrlParam context, MigrationInfo migrationInfo, QueryCriteria queryCriteria) {
        List<SyncData> syncDataList = new ArrayList<>();
        List<ObjectNode> admData = getAdmData(context,queryCriteria);

        String queryUrl = requestUrl(context, migrationInfo, MigrationType.QUERY.getCode());
        List<ObjectDigital> projectData = getProjectData(queryCriteria, ObjectDigital.class, queryUrl);
        List<ObjectNode> projObjectNodeList = JsonNodeUtils.toListNode(projectData, null, null);

        if(CollUtil.isEmpty(admData)){
            log.error(" ######################### dt_object adm已交付:"+ ResponseCode.C0320.getDesc() +" 同步结束 #########################");
        }

        //因获取的数据均有主键 使用更新
        String insertUrl = requestUrl(context, migrationInfo, MigrationType.UPDATE.getCode());
        if(CollUtil.isEmpty(projObjectNodeList)){
            //调用中台验证待新增的数据
            List<ObjectDigital> digitalList = validateInfoCode(admData, context);
            //处理并保存日志
            syncDataList.addAll(processDataForLog(DataMigrationResponse.success(digitalList), MigrationType.CREATE.getCode()));

            DataMigrationResponse dataMigrationResponse = insertBatch(admData, ObjectDigital.class, insertUrl);
            syncDataList.addAll(processDataForLog(dataMigrationResponse, MigrationType.CREATE.getCode()));

        }
        Map<String,Object> projectMap = toEntityMap(projObjectNodeList, ObjectDigital.class);
        Map<String,Object> admMap = toEntityMap(admData, ObjectDigital.class);
        //差集 新增
        Map<String,Object> doSubtractFromInsert = doSubtractAdd(admMap, projectMap);
        if(!CollUtil.isEmpty(doSubtractFromInsert)){
            List<ObjectNode> insertData = toList(doSubtractFromInsert, admData);
            //调用中台验证待新增的数据
            List<ObjectDigital> digitalList = validateInfoCode(insertData, context);
            //将验证结果放到日志
            syncDataList.addAll(processDataForLog(DataMigrationResponse.success(digitalList), MigrationType.CREATE.getCode()));

            //插入数据
            DataMigrationResponse dataMigrationResponse = insertBatch(insertData, ObjectDigital.class, insertUrl);
            //处理并保存日志
            syncDataList.addAll(processDataForLog(dataMigrationResponse, MigrationType.CREATE.getCode()));
        }

        //差集 删除
        Map<String,Object> doSubtractFromDelete = doSubtractDel(admMap, projectMap);
        if(!CollUtil.isEmpty(doSubtractFromDelete)){
            List<String> deleteIds = doSubtractFromDelete.entrySet().stream().map(en -> en.getKey()).collect(Collectors.toList());
            String delUrl = requestUrl(context, migrationInfo, MigrationType.DELETE.getCode());
            //处理删除的数据
            DataMigrationResponse dataMigrationResponse = deleteBatch(deleteIds, ObjectDigital.class, delUrl);
            List<String> successIds = (List<String>) dataMigrationResponse.getData();
            List<ObjectNode> delObjs = toListByIds(successIds, projObjectNodeList);
            dataMigrationResponse.setData(delObjs);
            //处理并保存日志
            syncDataList.addAll(processDataForLog(dataMigrationResponse, MigrationType.DELETE.getCode()));
        }

        //交集更新
        Map<String,Object> intersectionUpdateFrom = doIntersectionGetFrom(admMap, projectMap);
        Map<String,Object> intersectionUpdateTo = doIntersectionGetTo(admMap, projectMap);

        if(!CollUtil.isEmpty(intersectionUpdateFrom) && !CollUtil.isEmpty(intersectionUpdateTo)){
            Map<String, Object> compareData = compareData(intersectionUpdateFrom, intersectionUpdateTo);
            if(CollUtil.isNotEmpty(compareData) && compareData.size() > 0){
                List<ObjectNode> updateData = toList(compareData, admData);
                //调用中台验证待更新的数据
                List<ObjectDigital> digitalList = validateInfoCode(updateData, context);
                //将验证结果放到日志并保存
                syncDataList.addAll(processDataForLog(DataMigrationResponse.success(digitalList), MigrationType.UPDATE.getCode()));

                //更新
                String updateUrl = requestUrl(context, migrationInfo, MigrationType.UPDATE.getCode());
                DataMigrationResponse dataMigrationResponse = updateBatch(updateData, ObjectDigital.class, updateUrl);
                //处理并保存日志
                syncDataList.addAll(processDataForLog(dataMigrationResponse, MigrationType.UPDATE.getCode()));
            }
        }
        //处理并保存日志
        super.addSynLog(context, syncDataList);
    }

    /**
     * 迁移数据，记录excel数据
     *
     */
    private List<DataMigrationExcel> startMigrateForExcel(InstanceUrlParam context, MigrationInfo migrationInfo, QueryCriteria queryCriteria) {
        List<DataMigrationExcel> dataMigrationExcels = new ArrayList<>();
        List<ObjectNode> admData = getAdmData(context,queryCriteria);

        String queryUrl = requestUrl(context, migrationInfo, MigrationType.QUERY.getCode());
        List<ObjectDigital> projectData = getProjectData(queryCriteria, ObjectDigital.class, queryUrl);
        List<ObjectNode> objectNodeList = JsonNodeUtils.toListNode(projectData, null, null);

        if(CollUtil.isEmpty(admData)){
            return Collections.emptyList();
        }

        if(CollUtil.isEmpty(objectNodeList)){
            dataMigrationExcels.addAll(processDataForExcel(admData, MigrationType.CREATE.getCode()));
            return dataMigrationExcels;
        }
        Map<String,Object> projectMap = toEntityMap(objectNodeList, ObjectDigital.class);
        Map<String,Object> admMap = toEntityMap(admData, ObjectDigital.class);
        //差集 新增
        Map<String,Object> doSubtractFromInsert = doSubtractAdd(admMap, projectMap);
        if(!CollUtil.isEmpty(doSubtractFromInsert)){
            List<ObjectNode> insertData = toList(doSubtractFromInsert, admData);
            dataMigrationExcels.addAll(processDataForExcel(insertData, MigrationType.CREATE.getCode()));
        }

        //差集 删除
        Map<String,Object> doSubtractFromDelete = doSubtractDel(admMap, projectMap);
        if(!CollUtil.isEmpty(doSubtractFromDelete)){
            List<String> deleteIds = doSubtractFromDelete.entrySet().stream().map(en -> en.getKey()).collect(Collectors.toList());

            List<ObjectNode> deleteData = toListByIds(deleteIds, objectNodeList);
            dataMigrationExcels.addAll(processDataForExcel(deleteData, MigrationType.DELETE.getCode()));
        }

        //交集更新
        Map<String,Object> intersectionUpdateFrom = doIntersectionGetFrom(admMap, projectMap);
        Map<String,Object> intersectionUpdateTo = doIntersectionGetTo(admMap, projectMap);

        if(!CollUtil.isEmpty(intersectionUpdateFrom) && !CollUtil.isEmpty(intersectionUpdateTo)){
            Map<String, Object> compareData = compareData(intersectionUpdateFrom, intersectionUpdateTo);
            if(CollUtil.isNotEmpty(compareData) && compareData.size() > 0){
                List<ObjectNode> updateData = toList(compareData, admData);
                dataMigrationExcels.addAll(processDataForExcel(updateData, MigrationType.UPDATE.getCode()));
            }
        }

        return dataMigrationExcels;
    }

    /**
     * 获取adm交付的数据，默认获取有效数据
     *
     * @param context
     * @return
     */
    @Override
    public List<ObjectNode> getAdmData(InstanceUrlParam context, QueryCriteria queryCriteria) {
        List<ObjectNode> digitalList = DigitalObjectFacade.query(context.getGroupCode(),context.getProjectId(),context.getUserId(),context.getUserId(),queryCriteria);
        if(CollUtil.isEmpty(digitalList)){
            return Collections.emptyList();
        }
        return digitalList;
    }

    /**
     * 根据map转为需要处理的对象集合
     * @param fromMap
     * @param fromList
     * @return
     */
    public List<ObjectNode> toList(Map<String, Object> fromMap, List<ObjectNode> fromList) {
        if(CollUtil.isEmpty(fromMap) || CollUtil.isEmpty(fromList)){
            return Collections.emptyList();
        }
        return toListByIds(fromMap.keySet(), fromList);
    }

    /**
     * 根据对象ids转为需要处理的对象集合
     * @param ids
     * @param fromList
     * @return
     */
    public List<ObjectNode> toListByIds(Collection<String> ids, List<ObjectNode> fromList) {
        if(CollUtil.isEmpty(ids) || CollUtil.isEmpty(fromList)){
            return Collections.emptyList();
        }
        List<ObjectNode> results = new ArrayList<>(ids.size());

        Map<String, ObjectNode> objectDigitalMap =  new HashMap<>(fromList.size());
        for(ObjectNode node :  fromList){
            if(!node.get("id").isTextual()){
                continue;
            }
            objectDigitalMap.put(node.get("id").asText(), node);
        }

        for(String key : ids){
            if(objectDigitalMap.containsKey(key)){
                results.add(objectDigitalMap.get(key));
            }

        }
        return results;
    }



    /**
     * 拼接访问链接
     *
     * @param context
     * @param migrationInfo
     * @param type
     * @return
     */
    @Override
    public String requestUrl(InstanceUrlParam context, MigrationInfo migrationInfo, String type) {
        if(StrUtil.isBlank(migrationInfo.getTargetUrl())){
            return null;
        }
        StringBuilder url = new StringBuilder(migrationInfo.getTargetUrl());
        if(type.equals(MigrationType.QUERY.getCode())) {
            url.append(MigrationConstant.DT_OBJECT);
        }else if(type.equals(MigrationType.CREATE.getCode())){
            url.append(MigrationConstant.DT_OBJECT_INSERT);
        }else if(type.equals(MigrationType.UPDATE.getCode())){
            url.append(MigrationConstant.DT_OBJECT_UPDATE);
        }else{
            url.append(MigrationConstant.DT_OBJECT_DELETE);
        }
        return commonUrl(context, url.toString(), migrationInfo);
    }

    /**
     * 将目标集合处理成excel格式的集合  使用与insert/delete
     *
     * @param list
     * @return
     */
    @Override
    public List<DataMigrationExcel> processDataForExcel(Collection list, String type) {
        if(CollUtil.isEmpty(list)){
            return Collections.emptyList();
        }
        List<DataMigrationExcel> dataMigrationExcels = new ArrayList<>();
        for(Object obj : list){
            if(obj ==  null){
                continue;
            }
            ObjectDigital digital = JsonNodeUtils.toEntity((ObjectNode) obj, ObjectDigital.class, null);
            DataMigrationExcel excel = new DataMigrationExcel();
            excel.setId(digital.getId());
            excel.setName(String.join(StrUtil.DASHED, digital.getLocalId() == null ? "" : digital.getLocalId(), digital.getLocalName() == null ? "" : digital.getLocalName()));
            excel.setType(type);
            excel.setCreateTime(digital.getCreationTime().toString());
            excel.setUpdateTime(digital.getModifiedTime() == null ? "" :digital.getModifiedTime().toString());
            dataMigrationExcels.add(excel);
        }
        return dataMigrationExcels;
    }

    /**
     * 将目标集合处理成SyncData(日志)格式的集合  入库
     *
     * @param dataMigrationResponse
     * @param type    insert/delete
     * @return
     */
    @Override
    public List<SyncData> processDataForLog(DataMigrationResponse dataMigrationResponse, String type) {
        if(CollUtil.isEmpty(dataMigrationResponse.getData()) && CollUtil.isEmpty(dataMigrationResponse.getErrorMsg())){
            return Collections.emptyList();
        }
        List<SyncData> syncDataList = new ArrayList<>();
        if(CollUtil.isNotEmpty(dataMigrationResponse.getData())){
            for(Object obj : dataMigrationResponse.getData()){
                if(null == obj){
                    continue;
                }
                ObjectDigital digital = null;
                if(obj instanceof ObjectDigital){
                    digital = (ObjectDigital) obj;
                }else{
                    digital = JsonNodeUtils.toEntity((ObjectNode) obj, ObjectDigital.class, null);
                }
                SyncData syncData = new SyncData();
                syncData.setTableName(MigrationTable.DTOBJECT.getCode());
                syncData.setName(digital.getName());
                syncData.setObjectType(digital.getObjType());
                syncData.setType(type);//操作
                syncData.setClassCode(digital.getClassCode());
                syncData.setTargetId(digital.getId());
                syncData.setLocalId(digital.getLocalId());
                syncData.setLocalName(digital.getLocalName());
                if(StrUtil.isNotEmpty(digital.getMark())){
                    syncData.setError(digital.getMark());
                    syncData.setSign(1);
                }
                syncDataList.add(syncData);
            }
        }
        if(CollUtil.isNotEmpty(dataMigrationResponse.getErrorMsg())){
            syncDataList.addAll(addErrorInfoForLog(dataMigrationResponse.getErrorMsg(), MigrationTable.DTOBJECT.getCode()));
        }
        return syncDataList;
    }

    /**
     * 信息点值校验
     * @param voList
     */
    private List<ObjectDigital> validateInfoCode(List<ObjectNode> voList, InstanceUrlParam context) {
        if(CollUtil.isEmpty(voList)) {
            return Collections.emptyList();
        }
        ArrayNode arrayNode = JsonNodeUtils.toArrayNode(voList, null, null);
        return DigitalObjectFacade.validateInfoCodeAndGetErrorList(context.getGroupCode(),context.getProjectId(),context.getAppId(),context.getUserId(),arrayNode);
    }

}
