package com.persagy.bdtp.adm.service.impl;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.date.DatePattern;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.IdUtil;
import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ArrayNode;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.persagy.bdtp.adm.client.DicClient;
import com.persagy.bdtp.adm.client.RwdClient;
import com.persagy.bdtp.adm.common.AdmConst;
import com.persagy.bdtp.adm.common.AdmResult;
import com.persagy.bdtp.adm.dao.*;
import com.persagy.bdtp.adm.entity.*;
import com.persagy.bdtp.adm.entity.db.*;
import com.persagy.bdtp.adm.entity.oldadm.OProject;
import com.persagy.bdtp.adm.service.*;
import com.persagy.dmp.basic.model.QueryCriteria;
import com.persagy.dmp.common.constant.ResponseCode;
import com.persagy.dmp.common.constant.ValidEnum;
import com.persagy.dmp.common.exception.BusinessException;
import com.persagy.dmp.common.model.response.CommonResult;
import com.persagy.dmp.define.entity.RelationDefine;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.io.Resource;
import org.springframework.core.io.ResourceLoader;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.io.IOException;
import java.util.*;
import java.util.function.Supplier;

@Service
public class SyncAppImpl0 implements ISyncApp {

	@Autowired
	private IAdmObjectService objectService;

	@Autowired
	private IQueryFromOldAdm queryOldAdm;

	@Autowired
	private RwdClient rwdClient;

	@Autowired
	private DicClient dicClient;

	@Autowired
	private IConfigService configService;


	@Autowired
	private AdmObjectMapper admObjMapper;

	@Autowired
	private AdmRelationMapper relationMapper;

	@Autowired
	private AdmPipeMapper pipeMapper;

	@Autowired
	private AdmJobSpaceMapper jobSpaceMapper;

	@Autowired
	private AdmProblemArchMapper problemArchMapper;

	@Autowired
	private AdmProblemEquipMapper problemEquipMapper;

	@Autowired
	private AdmFileMapper fileMapper;

	@Autowired
	private AdmQrCodeMapper qrCodeMapper;

	@Autowired
	private AdmServeAreaMapper serveAreaMapper;

	@Autowired
	private ISyncModel syncModel;

	@Autowired
	private ObjectMapper objectMapper;

	@Autowired
	private IAdmLogService logService;

	@Autowired
	ResourceLoader resourceLoader;

	@Override
	public String getClientId(String userId) {
		return IdUtil.fastSimpleUUID();
	}

	@Override
	public Dict downloadDict(String groupCode, String projectId, String userId) {
		//临时方案，腾讯云生产环境没有数据字典4.0，从本地文件加载
		try{
			Resource resource = resourceLoader.getResource("classpath:dict.json");
			return objectMapper.readValue(resource.getInputStream(), Dict.class);
		} catch (Exception e) {
			e.printStackTrace();
		}

		return queryDict(groupCode, projectId, userId, true, true);
	}

	@Override
	public Map<String, Object> downloadConfig(String groupCode, String projectId, String userId) {
		//配置数据查询
		List<AdmM2dEquip> m2dTypes = configService.queryM2dEquip(projectId);
		List<AdmInfosConfig> infosConfig = configService.queryInfosConfig(projectId);
		List<AdmRelsConfig> relConfig = configService.queryRelsConfig(projectId);
		List<AdmPipeConfig> pipeConfig = configService.queryPipeConfig(projectId);
		List<AdmConfig> config = configService.queryCommonConfig(projectId);
		List<AdmComponent> component = configService.queryComponent(projectId);
		List<AdmContainerConfig> containerConfig = configService.queryContainerConfig(projectId);

		HashMap<String, Object> data = new HashMap<>();
		data.put("m2dTypes", m2dTypes);
		data.put("infosConfig", serializeJsonProp(infosConfig));
		data.put("relConfig", relConfig);
		data.put("pipeConfig", pipeConfig);
		data.put("config", serializeJsonProp(config));
		data.put("component", component);
		data.put("containerConfig", containerConfig);

		return data;
	}

	@Override
	public Dict queryDict(String groupCode, String projectId, String userId, boolean includeRel, boolean includeInfos){
		boolean newDict = "JD".equalsIgnoreCase(groupCode);

		//从知识库服务获取专业分类
		QueryCriteria majorCriteria = ServiceUtil.getQueryCriteria(objectMapper);
		majorCriteria.getCriteria().put("type", "major");
		List<Map<String, Object>> majorList = ServiceUtil.call (() -> dicClient.dataQuery(groupCode, projectId, AdmConst.APP_ID, userId, majorCriteria));

		//查询系统和设备定义
		QueryCriteria criteria = ServiceUtil.getQueryCriteria(objectMapper, "system", "equipment");
		List<TypeDef> typeList = ServiceUtil.queryAllPage(() -> rwdClient.queryObjectType(groupCode, projectId, AdmConst.APP_ID, userId, criteria), criteria, new Pagination(500));

		//查询重命名设备类型
		ObjectNode renameNode = !newDict ? configService.queryRenameEquipTypes(projectId) : null;

		LinkedList<TypeDef> sysList = new LinkedList<>();
		LinkedList<TypeDef> equipList = new LinkedList<>();
		for(TypeDef def : typeList){
			if(def.getObjType().equals("system"))
				sysList.add(def);
			else if(def.getObjType().equals("equipment")) {
				if(!newDict) {
					if (renameNode != null) { //设备类型重命名
						JsonNode n = renameNode.get(def.getCode());
						if(n != null)
							def.setName(n.asText());
					}
				}
				equipList.add(def);
			}
		}

		//加载新增的自定义设备类
//		try{
//			Resource resource = resourceLoader.getResource("classpath:equip.json");
//			List<TypeDef> types = objectMapper.readValue(resource.getInputStream(), new TypeReference<List<TypeDef>>(){});
//			equipList.addAll(types);
//		}catch (Exception e) {
//
//		}
		if(!newDict) {
			ArrayNode arr = configService.queryNewEquipTypes(projectId);
			if(arr != null) {
				try{
					List<TypeDef> types = objectMapper.readValue(objectMapper.writeValueAsString(arr), new TypeReference<List<TypeDef>>(){});
					equipList.addAll(types);
				}catch (Exception e) {

				}
			}
		}

		Dict data = new Dict();
		data.setMajor(majorList);
		data.setSystem(sysList);
		data.setEquipment(equipList);

		//查询关系定义
		if(includeRel) {
			List<RelationDefine> relList = ServiceUtil.call(() -> rwdClient.queryRelDef(groupCode, projectId, AdmConst.APP_ID, userId, new QueryCriteria()));
			data.setRelation(relList);
		}
		//查询信息点定义
		if(includeInfos) {
			QueryCriteria qc = new QueryCriteria();
			qc.setCriteria(objectMapper.createObjectNode());
			List<InfoDef> funcList = ServiceUtil.call(() -> rwdClient.queryFunc(groupCode, projectId, AdmConst.APP_ID, userId, qc));
			data.setInfos(funcList);
		}

		return data;
	}

	@Override
	public Map<String, Object> downloadFrameData(String groupCode, String projectId, String userId) {
		List<OProject> prjList = null;
		String token = queryOldAdm.login();
		if(StrUtil.isNotBlank(token))
			prjList = queryOldAdm.queryProject(token, groupCode, projectId);
		else
			throw new CallException(new AdmResult(ResponseCode.A0220.getCode(), "ADM platform: " + ResponseCode.A0220.getDesc()));

		String[] prjIds = new String[prjList.size()];
		for (int i = 0; i < prjIds.length; i++) {
			prjIds[i] = prjList.get(i).getId();
		}
		List<AdmObject> bdAndFl = objectService.queryBdAndFl(prjIds);

		HashMap<String, Object> data = new HashMap<>();
		data.put("projects", prjList);
		data.put("buildingsAndFloors", serializeJsonProp(bdAndFl));
		return data;
	}

	@Override
	public BuildingData downloadBuildingData(String projectId, String buildingId, String uploadFlag, String lastDownloadTime) {
		List<AdmObject> bdData = admObjMapper.selectList(buildDownloadQueryWrapper(new QueryWrapper<AdmObject>(), projectId, buildingId, uploadFlag, lastDownloadTime));
		List<AdmRelation> bdRel = relationMapper.selectList(buildDownloadQueryWrapper(new QueryWrapper<AdmRelation>(), projectId, buildingId, uploadFlag, lastDownloadTime));
		List<AdmPipe> pipes = pipeMapper.selectList(buildDownloadQueryWrapper(new QueryWrapper<AdmPipe>(), projectId, buildingId, uploadFlag, lastDownloadTime));

		//任务相关数据查询
		List<AdmJobSpace> jobSpace = jobSpaceMapper.selectList(buildDownloadQueryWrapper(new QueryWrapper<AdmJobSpace>(), projectId, buildingId, uploadFlag, lastDownloadTime));
		List<AdmProblemArch> problemArch = problemArchMapper.selectList(buildDownloadQueryWrapper(new QueryWrapper<AdmProblemArch>(), projectId, buildingId, uploadFlag, lastDownloadTime));
		List<AdmProblemEquip> problemEquip = problemEquipMapper.selectList(buildDownloadQueryWrapper(new QueryWrapper<AdmProblemEquip>(), projectId, buildingId, uploadFlag, lastDownloadTime));
		List<AdmFile> file = fileMapper.selectList(buildDownloadQueryWrapper(new QueryWrapper<AdmFile>(), projectId, buildingId, uploadFlag, lastDownloadTime));
		List<AdmQrCode> qrCode = qrCodeMapper.selectList(buildDownloadQueryWrapper(new QueryWrapper<AdmQrCode>(), projectId, buildingId, uploadFlag, lastDownloadTime));
		List<AdmServeArea> serveArea = serveAreaMapper.selectList(buildDownloadQueryWrapper(new QueryWrapper<AdmServeArea>(), projectId, buildingId, uploadFlag, lastDownloadTime));

		BuildingData data = new BuildingData();
		data.setBuildingId(buildingId);

		data.setObjects(serializeJsonProp(bdData));
		data.setRelations(bdRel);
		data.setPipes(serializeJsonProp(pipes));

		data.setJobSpace(serializeJsonProp(jobSpace));
		data.setProblemArch(serializeJsonProp(problemArch));
		data.setProblemEquip(problemEquip);
		data.setFile(file);
		data.setQrCode(serializeJsonProp(qrCode));
		data.setServeArea(serveArea);
		data.setTimestamp(DateUtil.format(new Date(), DatePattern.PURE_DATETIME_PATTERN));

		return data;
	}

	private <T> QueryWrapper<T> buildDownloadQueryWrapper(QueryWrapper<T> wrapper, String projectId, String buildingId, String uploadFlag, String lastDownloadTime){
		wrapper.eq("project_id", projectId).eq("building_id", buildingId);
		applyUploadFlag(wrapper, uploadFlag,lastDownloadTime);
		return wrapper;
	}

	private void applyUploadFlag(QueryWrapper<?> wrapper, String uploadFlag, String lastDownloadTime){
		if (StrUtil.isNotBlank(lastDownloadTime)) { //非初始化下载
			if (StrUtil.isNotBlank(uploadFlag))
				wrapper.and(w -> w.isNull("upload_flag").or().ne("upload_flag", uploadFlag));

			wrapper.and(
					w -> w.isNotNull("modified_time").gt("modified_time", lastDownloadTime)
							.or()
							.isNull("modified_time").gt("creation_time", lastDownloadTime)
			);
		}
	}

	@Override
	public BuildingData downloadBuildingData(String groupCode, String projectId, String userId, String uploadFlag, String buildingId, Long bdtpDownloadTs, Long admDownloadTs) {
		throw new RuntimeException("not supported");
	}

	@Override
	public ProjectData downloadProjectData(String groupCode, String projectId, String userId, String uploadFlag, Long bdtpDownloadTs, Long admDownloadTs) {
		throw new RuntimeException("not supported");
	}

	@Override
	@Transactional(rollbackFor = Exception.class)
	public UploadRtn uploadData(UploadData uploadData, String groupCode, String projectId, String userId, String uploadFlag) {
		UploadRtn rtn = new UploadRtn();
		String timestamp = DateUtil.format(new Date(), DatePattern.PURE_DATETIME_PATTERN);

		if (CollUtil.isNotEmpty(uploadData.getObjects())) {
			List<AdmObject> dbObjs = admObjMapper.selectList(new QueryWrapper<AdmObject>().eq("building_id", uploadData.getBuildingId()).eq("valid", ValidEnum.TRUE.getType()));
			List<AdmObject> expired = uploadAdmData(deserializeJsonProp(toEntities(uploadData.getObjects(), AdmObject.class)), dbObjs, uploadFlag, timestamp, admObjMapper, "object");
			rtn.setExpiredObjectIds(buildExpiredIds(expired));
		}
		if (CollUtil.isNotEmpty(uploadData.getRelations())) {
			List<AdmRelation> dbObjs = relationMapper.selectList(new QueryWrapper<AdmRelation>().eq("building_id", uploadData.getBuildingId()).eq("valid", ValidEnum.TRUE.getType()));
			List<AdmRelation> expired = uploadAdmData(toEntities(uploadData.getRelations(), AdmRelation.class), dbObjs, uploadFlag, timestamp, relationMapper, "relation");
			rtn.setExpiredRelationIds(buildExpiredIds(expired));
		}

		if (CollUtil.isNotEmpty(uploadData.getPipes())) {
			List<AdmPipe> dbObjs = pipeMapper.selectList(new QueryWrapper<AdmPipe>().eq("building_id", uploadData.getBuildingId()).eq("valid", ValidEnum.TRUE.getType()));
			List<AdmPipe> expired = uploadAdmData(deserializeJsonProp(uploadData.getPipes()), dbObjs, uploadFlag, timestamp, pipeMapper, "pipe");
			rtn.setExpiredPipeIds(buildExpiredIds(expired));
		}

		if (CollUtil.isNotEmpty(uploadData.getJobSpace())) {
			List<AdmJobSpace> dbObjs = jobSpaceMapper.selectList(new QueryWrapper<AdmJobSpace>().eq("building_id", uploadData.getBuildingId()).eq("valid", ValidEnum.TRUE.getType()));
			List<AdmJobSpace> expired = uploadAdmData(deserializeJsonProp(uploadData.getJobSpace()), dbObjs, uploadFlag, timestamp, jobSpaceMapper, null);
			rtn.setExpiredJobSpaceIds(buildExpiredIds(expired));
		}
		if (CollUtil.isNotEmpty(uploadData.getProblemArch())) {
			List<AdmProblemArch> dbObjs = problemArchMapper.selectList(new QueryWrapper<AdmProblemArch>().eq("building_id", uploadData.getBuildingId()).eq("valid", ValidEnum.TRUE.getType()));
			List<AdmProblemArch> expired = uploadAdmData(deserializeJsonProp(uploadData.getProblemArch()), dbObjs, uploadFlag, timestamp, problemArchMapper, null);
			rtn.setExpiredProblemArchIds(buildExpiredIds(expired));
		}
		if (CollUtil.isNotEmpty(uploadData.getProblemEquip())) {
			List<AdmProblemEquip> dbObjs = problemEquipMapper.selectList(new QueryWrapper<AdmProblemEquip>().eq("building_id", uploadData.getBuildingId()).eq("valid", ValidEnum.TRUE.getType()));
			List<AdmProblemEquip> expired = uploadAdmData(uploadData.getProblemEquip(), dbObjs, uploadFlag, timestamp, problemEquipMapper, null);
			rtn.setExpiredProblemEquipIds(buildExpiredIds(expired));
		}
		if (CollUtil.isNotEmpty(uploadData.getFile())) {
			List<AdmFile> dbObjs = fileMapper.selectList(new QueryWrapper<AdmFile>().eq("building_id", uploadData.getBuildingId()).eq("valid", ValidEnum.TRUE.getType()));
			List<AdmFile> expired = uploadAdmData(uploadData.getFile(), dbObjs, uploadFlag, timestamp, fileMapper, null);
			rtn.setExpiredFileIds(buildExpiredIds(expired));
		}
		if (CollUtil.isNotEmpty(uploadData.getQrCode())) {
			List<AdmQrCode> dbObjs = qrCodeMapper.selectList(new QueryWrapper<AdmQrCode>().eq("building_id", uploadData.getBuildingId()).eq("valid", ValidEnum.TRUE.getType()));
			List<AdmQrCode> expired = uploadAdmData(deserializeJsonProp(uploadData.getQrCode()), dbObjs, uploadFlag, timestamp, qrCodeMapper, null);
			rtn.setExpiredQrCodeIds(buildExpiredIds(expired));
		}
		if (CollUtil.isNotEmpty(uploadData.getServeArea())) {
			List<AdmServeArea> dbObjs = serveAreaMapper.selectList(new QueryWrapper<AdmServeArea>().eq("building_id", uploadData.getBuildingId()).eq("valid", ValidEnum.TRUE.getType()));
			List<AdmServeArea> expired = uploadAdmData(uploadData.getServeArea(), dbObjs, uploadFlag, timestamp, serveAreaMapper, null);
			rtn.setExpiredServeAreaIds(buildExpiredIds(expired));
		}

		rtn.setTimestamp(timestamp);
		rtn.setBuildingId(uploadData.getBuildingId());
		return rtn;
	}

	private <T extends BaseAdmDataEntity> List<T> toEntities(List<ObjectNode> list, Class<T> cls){
		List<T> entities = new ArrayList<>(list.size());
		try {
			for(ObjectNode node : list) {
				entities.add(objectMapper.readValue(node.toString(), cls));
			}
		} catch (IOException e) {
			throw new BusinessException(ResponseCode.A0427.getCode(), ResponseCode.A0427.getDesc());
		}
		return entities;
	}

	private <T extends BaseAdmDataEntity> List<T> uploadAdmData(List<T> objs, List<T> dbObjs, String uploadFlag, String timestamp, BaseMapper<T> mapper, String d2mType){
		List<T> expired = new ArrayList<>();
		HashMap<String, T> dbMap = new HashMap<>(dbObjs.size());
		for(T dbObj : dbObjs) {
			dbMap.put(dbObj.getId(), dbObj);
		}

		List<T> msgAdd = new ArrayList<>();
		List<T> msgModify = new ArrayList<>();
		List<T> msgRemove = new ArrayList<>();
		for (T obj : objs) {
			if(obj == null)
				continue;

			logService.saveUploadLog(new AdmUploadLog());

			T dbObj = dbMap.get(obj.getId());
			if (dbObj == null) {
				if(obj.getValid().intValue() == ValidEnum.FALSE.getType())
					continue;

				obj.setCreationTime(timestamp);
				obj.setCreator(AdmConst.CREATOR_APP);
				//obj.setUploadFlag(uploadFlag);

				if(d2mType != null)
					setDeliveryDataProps((BaseDeliveryEntity) obj, null);

				mapper.insert(obj);

				if(d2mType != null)
					msgAdd.add(obj);
			} else {
//				String dbTs;
//				String ts;
//				if(dbObj.getModifiedTime() != null){
//					dbTs = dbObj.getModifiedTime();
//					ts = obj.getModifiedTime();
//				} else {
//					dbTs = dbObj.getCreationTime();
//					ts = obj.getCreationTime();
//				}
//				if (!dbTs.equals(ts)) {
//					//TODO 时间戳不一致，离线数据无效，暂时跳过处理
//					expired.add(obj);
//				} else {
					obj.setModifiedTime(timestamp);
					obj.setModifier(AdmConst.CREATOR_APP);
					//obj.setUploadFlag(uploadFlag);

					if(d2mType != null)
						setDeliveryDataProps((BaseDeliveryEntity) obj, null);

					//合并信息点
					if(dbObj instanceof AdmObject && obj instanceof AdmObject)
						((AdmObject) obj).setInfos(mergeInfos(((AdmObject) obj).getInfos(), ((AdmObject) dbObj).getInfos()));
					else if(dbObj instanceof AdmPipe && obj instanceof AdmPipe)
						((AdmPipe) obj).setInfos(mergeInfos(((AdmPipe) obj).getInfos(), ((AdmPipe) dbObj).getInfos()));

					mapper.updateById(obj);

					if(d2mType != null) {
						if(obj.getValid().intValue() == ValidEnum.FALSE.getType())
							msgRemove.add(obj);
						else
							msgModify.add(obj);
					}
//				}
			}
		}

		if(d2mType != null)
			sendD2mMessage(d2mType, msgAdd, msgModify, msgRemove);

		return expired;
	}

	private void setDeliveryDataProps(BaseDeliveryEntity obj, BaseDeliveryEntity dbObj){
		obj.setD2mState(ValidEnum.FALSE.getType());
		obj.setDeliveryState(ValidEnum.FALSE.getType());

		if(dbObj != null) {
			obj.setClientId(dbObj.getClientId());
			obj.setClientDevice(dbObj.getClientDevice());
		}
	}

	private ObjectNode mergeInfos(ObjectNode infos, ObjectNode dbInfos){
		if(dbInfos != null) {
			if(infos != null)
				dbInfos.setAll(infos);
			return dbInfos;
		} else
			return infos;
	}

	private void sendD2mMessage(String objectType, List msgAdd, List msgModify, List msgRemove){
		D2mMessage message = new D2mMessage(objectType, "", "");
		message.setAddItems(msgAdd);
		message.setModifyItems(msgModify);
		message.setRemoveItems(msgRemove);

		if(message.isNotEmpty())
			syncModel.sendMessageToModel(message);
	}

	private List<String> buildExpiredIds(List<? extends BaseAdmDataEntity> expired){
		if(expired.size() > 0) {
			List<String> ids = new ArrayList<>();
			for(BaseAdmDataEntity entity : expired) {
				ids.add(entity.getId());
			}
			return ids;
		}
		return null;
	}

	private <T extends BaseAdmEntity> List<T> serializeJsonProp(List<T> list){
		//app端完善，不需要做tostr处理了
//		for(BaseAdmEntity entity : list) {
//			entity.serializeJsonProp();
//		}
		return list;
	}

	private <T extends BaseAdmEntity> List<T> deserializeJsonProp(List<T> list){
		for(BaseAdmEntity entity : list) {
			entity.deserializeJsonProp();
		}
		return list;
	}

}
