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

import cn.hutool.core.date.DatePattern;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.ArrayUtil;
import cn.hutool.core.util.IdUtil;
import cn.hutool.core.util.ReflectUtil;
import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
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.OldAdmClient;
import com.persagy.bdtp.adm.common.AdmConst;
import com.persagy.bdtp.adm.common.AdmResult;
import com.persagy.bdtp.adm.dao.AdmJobSpaceMapper;
import com.persagy.bdtp.adm.dao.AdmObjectMapper;
import com.persagy.bdtp.adm.entity.db.AdmJobSpace;
import com.persagy.bdtp.adm.entity.db.AdmObject;
import com.persagy.bdtp.adm.entity.oldadm.*;
import com.persagy.bdtp.adm.service.CallException;
import com.persagy.bdtp.adm.service.IAdmObjectService;
import com.persagy.bdtp.adm.service.IQueryFromOldAdm;
import com.persagy.bdtp.adm.service.ISyncOldAdm;
import com.persagy.bdtp.adm.util.GeomUtil;
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.helper.SpringHelper;
//import com.persagy.dmp.file.client.FileClientFacade;
//import com.persagy.dmp.file.model.FileInfo;
//import com.persagy.dmp.file.model.FileInfoCreator;
//import com.persagy.dmp.storage.utils.FileStorageHelper;
import feign.Response;
import org.locationtech.jts.geom.Polygon;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

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

@Service
public class SyncOldAdmImpl implements ISyncOldAdm {

	@Autowired
	private IQueryFromOldAdm queryOldAdm;

	@Autowired
	private IAdmObjectService objectService;

	@Autowired
	private AdmObjectMapper objectMapper;

	@Autowired
	private AdmJobSpaceMapper jobSpaceMapper;

	@Override
	@Transactional(rollbackFor = Exception.class)
	public Map<String, Object> syncData(String groupCode, String projectId) {
		String token = queryOldAdm.login();
		if(StrUtil.isNotBlank(token)){
			List<OBuilding> bdList = queryOldAdm.queryBdAnFl(token, groupCode, projectId);

			List<OSpace> spList = queryOldAdm.querySpace(token, groupCode, projectId);
			spList = spList.stream().filter(space -> GeomUtil.checkPolygon(space.getOutline())).collect(Collectors.toList());

			List<OEquip> eqList  = queryOldAdm.queryEquip(token, groupCode, projectId);

			List<AdmObject> bdObjs = new ArrayList<>(bdList.size());
			List<AdmObject> flObjs = new ArrayList<>();
			List<AdmObject> spObjs = new ArrayList<>(spList.size());
			List<AdmObject> eqObjs = new ArrayList<>(eqList.size());

			transEntities(bdObjs, bdList, (bdObj, oBd) -> {
				setBdData(bdObj, (OBuilding) oBd);
				if(((OBuilding) oBd).getFloor() != null)
					transEntities(flObjs, ((OBuilding) oBd).getFloor(), (flObj, oFl) -> setFlData(flObj, (OFloor) oFl));
			});

			transEntities(spObjs, spList, (spObj, oSp) -> setSpData(spObj, (OSpace) oSp));
			transEntities(eqObjs, eqList, (eqObj, oEq) -> setEqData(eqObj, (OEquip) oEq));

			Map<String, String> floorIdMap = new HashMap<>();
			List<AdmObject> dbBds = objectService.queryM2dByObjType(projectId, AdmConst.OBJ_TYPE_BUILDING);
			List<AdmObject> dbFls = objectService.queryM2dByObjType(projectId, AdmConst.OBJ_TYPE_FLOOR);
			List<AdmObject> dbSps = objectService.queryM2dByObjType(projectId, AdmConst.OBJ_TYPE_SPACE);
			List<AdmObject> dbEqs = objectService.queryM2dByObjType(projectId, AdmConst.OBJ_TYPE_EQUIPMENT);

			List<AdmObject>[] operations = new List[]{new ArrayList<>(), new ArrayList<>()};

			syncObjs(bdObjs, dbBds, operations, 1, floorIdMap);
			syncObjs(flObjs, dbFls, operations, 2, floorIdMap);
			syncObjs(spObjs, dbSps, operations, 3, floorIdMap);
			syncObjs(eqObjs, dbEqs, operations, 4, floorIdMap);

			objectService.insert(operations[0]);
			objectService.update(operations[1]);

			/*uploadFloorMap(flObjs, groupCode, projectId);*/
			HashMap<String, Object> rtn = new HashMap<>();
			rtn.put(AdmConst.OBJ_TYPE_BUILDING, bdObjs.size());
			rtn.put(AdmConst.OBJ_TYPE_FLOOR, flObjs.size());
			rtn.put(AdmConst.OBJ_TYPE_SPACE, spObjs.size());
			rtn.put(AdmConst.OBJ_TYPE_EQUIPMENT, eqObjs.size());

			return rtn;
		}
		throw new CallException(new AdmResult(ResponseCode.A0220.getCode(), "ADM platform: " + ResponseCode.A0220.getDesc()));
	}

	//TODO 设备同步时，按正逆向规则过滤
	private void syncObjs(List<AdmObject> objs, List<AdmObject> dbItems, List<AdmObject>[] operations, int objMark, Map<String, String> floorIdMap){
		HashMap<String, AdmObject> dbMap = new HashMap<>();
		for (AdmObject dbItem : dbItems){
			dbMap.put(getFixedIdForSync(dbItem, objMark), dbItem);
		}

		String timestamp =  DateUtil.format(new Date(), DatePattern.PURE_DATETIME_PATTERN);

		for(AdmObject obj : objs) {
			AdmObject dbItem = dbMap.remove(getFixedIdForSync(obj, objMark));
			if(dbItem == null) {
				obj.setCreator(AdmConst.CREATOR_SYNC);
				obj.setModifiedTime(timestamp); //保留从老adm同步过来的createTime，设置updateTime
				obj.setD2mState(ValidEnum.TRUE.getType());
				obj.setDeliveryState(ValidEnum.FALSE.getType());

				if(objMark == 3 || objMark == 4) { //新增空间|设备
					if(floorIdMap.size() > 0){
						String fixedFloorId = floorIdMap.get(obj.getFloorId());
						if(fixedFloorId != null)
							obj.setFloorId(fixedFloorId);
					}
				}

				operations[0].add(obj); //add
			} else {
				AdmObject updateItem = mergeObj(obj, dbItem, timestamp, objMark);
				if(updateItem != null)
					operations[1].add(updateItem); //modify

				if(objMark == 2 && !obj.getId().equals(dbItem.getId())) {
					floorIdMap.put(obj.getId(), dbItem.getId());
				}
			}
		}

		if (dbMap.size() > 0) { //remove
			for(AdmObject obj : dbMap.values()){
				obj.setValid(ValidEnum.FALSE.getType());
				obj.setModifiedTime(timestamp);
				obj.setModifier(AdmConst.CREATOR_SYNC);
				obj.setUploadFlag(null);
				operations[1].add(obj);
			}
		}
	}

	/**
	 * 同步数据时固定的id，优先取bimId，没有bimId取数据id
	 */
	private String getFixedIdForSync(AdmObject obj, int objMark){
		String bimId = null;
		JsonNode node = obj.getInfo("bimId");
		if(node != null)
			bimId = node.asText();

		if(StrUtil.isNotBlank(bimId))
			return bimId;

		if(objMark == 1) //建筑
			return obj.getLocalId();
		else if(objMark == 2) //楼层
			return obj.getBuildingId() + ":" + obj.getLocalId();

		return obj.getId();
	}

	private AdmObject mergeObj(AdmObject obj, AdmObject dbItem, String timestamp, int objMark){
		if(objNotChange(obj, dbItem))
			return null;

		//合并属性和信息点
//		if(objMark == 3) {//space
//
//		} else if(objMark == 4) {//equipment
//
//		}
		if (obj.getInfos() != null) {
			if (dbItem.getInfos() == null)
				dbItem.setInfos(obj.getInfos());
			else
				dbItem.getInfos().setAll(obj.getInfos());
		}

		dbItem.setModifiedTime(timestamp);
		dbItem.setModifier(AdmConst.CREATOR_SYNC);
		dbItem.setUploadFlag(null);
		dbItem.setD2mState(ValidEnum.TRUE.getType());
		dbItem.setDeliveryState(ValidEnum.FALSE.getType());

		return dbItem;
	}

	private boolean objNotChange(AdmObject obj, AdmObject dbItem){
		//TODO 数据无变化判断
		return false;
	}

	private <T extends OBase> void transEntities(List<AdmObject> targetList, List<T> list, SetDataAction setDataAction){
		if(list != null && list.size() > 0) {
			for(T item : list) {
				AdmObject obj = buildAdmObject(item);
				setDataAction.setData(obj, item);
				targetList.add(obj);
			}
		}
	}

	private AdmObject buildAdmObject(OBase base){
		AdmObject obj = new AdmObject();
		obj.setId(base.getId());
		obj.setLocalId(base.getLocalId());
		obj.setName(base.getName());
		obj.setLocalName(base.getLocalName());
		obj.setObjType(base.getObjectType());

		if (base.getInfos() != null)
			obj.setInfos(mapToNode(base.getInfos()));

		if(base.getCreateTime() != null) //同步数据中没有createTime时，设置为当前时间
			obj.setCreationTime(DateUtil.format(new Date(), DatePattern.PURE_DATETIME_PATTERN));

		obj.setValid(ValidEnum.TRUE.getType());

		return obj;
	}

	private void setBdData(AdmObject obj, OBuilding bd){
		obj.setProjectId(bd.getProjectId());
		obj.setClassCode(bd.getObjectType());
		//obj.setBuildingId(bd.getId());

		if(bd.getDefaultQRCode() != null)
			getInfos(obj).put("defaultQRCode", bd.getDefaultQRCode());
		if(bd.getBuildQRCode() != null)
			getInfos(obj).put("buildQRCode", bd.getBuildQRCode());
	}

	private void setFlData(AdmObject obj, OFloor fl){
		obj.setProjectId(fl.getProjectId());
		obj.setBuildingId(fl.getBuildingId());
		//obj.setFloorId(fl.getId());
		obj.setClassCode(fl.getObjectType());

		setInfos(obj, fl, "projectId", "buildingId", "floorId");
	}

	private void setSpData(AdmObject obj, OSpace sp) {
		obj.setProjectId(sp.getProjectId());
		obj.setBuildingId(sp.getBuildingId());
		obj.setFloorId(sp.getFloorId());
		obj.setClassCode(sp.getClassCode());
		obj.setObjType(AdmConst.OBJ_TYPE_SPACE);

		setInfos(obj, sp, "projectId", "buildingId", "floorId", "classCode");
	}

	private void setEqData(AdmObject obj, OEquip eq) {
		obj.setProjectId(eq.getProjectId());
		obj.setBuildingId(eq.getBuildingId());
		obj.setFloorId(eq.getFloorId());
		obj.setClassCode(eq.getClassCode());

		setInfos(obj, eq, "projectId", "buildingId", "floorId", "propertyId", "classCode");
	}

	private void setInfos(AdmObject obj, OBase oEntity, String... ignoreFields){
		Field[] fields = oEntity.getClass().getDeclaredFields();
		for (Field field : fields) {
			if(ignoreFields.length == 0 || !ArrayUtil.contains(ignoreFields, field.getName())) {
				Object val = ReflectUtil.getFieldValue(oEntity, field);
				if(val != null) {
					ObjectNode infos = getInfos(obj);
					if(val instanceof String)
						infos.put(field.getName(), (String)val);
					else if(val instanceof Integer)
						infos.put(field.getName(), (Integer)val);
					else if(val instanceof Float)
						infos.put(field.getName(), (Float)val);
					else if(val instanceof Double)
						infos.put(field.getName(), (Double)val);
					else if(val instanceof List)
						infos.putArray(field.getName()).addAll(listToNode((List)val));
					else if(val instanceof Map)
						infos.putObject(field.getName()).setAll(mapToNode((Map)val));
				}
			}
		}
	}

	private ObjectNode getInfos(AdmObject obj) {
		ObjectNode objectNode = obj.getInfos();
		if(objectNode == null){
			ObjectMapper objectMapper = SpringHelper.getBean(ObjectMapper.class);
			objectNode = objectMapper.createObjectNode();
			obj.setInfos(objectNode);
		}
		return objectNode;
	}

	private ObjectNode mapToNode(Map<String, Object> map){
		ObjectMapper objectMapper = SpringHelper.getBean(ObjectMapper.class);
		try {
			String json = objectMapper.writeValueAsString(map);
			return objectMapper.readValue(json, ObjectNode.class);
		} catch (IOException e) {
			throw new BusinessException(ResponseCode.A0427.getCode(), ResponseCode.A0427.getDesc());
		}
	}

	private ArrayNode listToNode(List<Object> list){
		ObjectMapper objectMapper = SpringHelper.getBean(ObjectMapper.class);
		try {
			String json = objectMapper.writeValueAsString(list);
			return objectMapper.readValue(json, ArrayNode.class);
		} catch (IOException e) {
			throw new BusinessException(ResponseCode.A0427.getCode(), ResponseCode.A0427.getDesc());
		}
	}

	interface SetDataAction {
		void setData(AdmObject obj, OBase oldAdmEntity);
	}

	/**
	 * 创建/合并任务空间数据
	 */
	@Override
	@Transactional(rollbackFor = Exception.class)
	public void handleJobSpace(String projectId){
		QueryWrapper<AdmObject> queryWrapper = new QueryWrapper<>();
		queryWrapper.eq("project_id", projectId).eq("obj_type", AdmConst.OBJ_TYPE_SPACE).eq("valid", ValidEnum.TRUE.getType()).orderByAsc("floor_id");
		List<AdmObject> spaceList = objectMapper.selectList(queryWrapper);

		QueryWrapper<AdmJobSpace> wrapper = new QueryWrapper<>();
		wrapper.eq("project_id", projectId).eq("valid", ValidEnum.TRUE.getType());
		List<AdmJobSpace> jobSpaceList = jobSpaceMapper.selectList(wrapper);

		//空间按层分组，优先使用OtherZone，缺省GeneralZone
		LinkedHashMap<String, List<AdmObject>> map = new LinkedHashMap<>();
		String floorId = null;
		List<AdmObject> generalList = null;
		List<AdmObject> otherList = null;
		for(AdmObject sp : spaceList){
			if(floorId == null || !floorId.equals(sp.getFloorId())) {
				if(floorId != null)
					map.put(floorId, otherList.size() > 0 ? otherList : generalList);

				floorId = sp.getFloorId();
				generalList = new ArrayList<>();
				otherList = new ArrayList<>();
			}

			String zone = sp.getClassCode();
			if("GeneralZone".equals(zone))
				generalList.add(sp);
			else if("OtherZone".equals(zone))
				otherList.add(sp);
		}
		if(floorId != null)
			map.put(floorId, otherList.size() > 0 ? otherList : generalList);

		List<AdmJobSpace> toCreate = new ArrayList<>();
		List<AdmJobSpace> toModify = new ArrayList<>();

		String time = DateUtil.format(new Date(), DatePattern.PURE_DATETIME_PATTERN);

		//按楼层进行空间匹配操作
		for (Map.Entry<String, List<AdmObject>> entry : map.entrySet()) {
			floorId = entry.getKey();

			HashMap<String, AdmJobSpace> jobMap = new HashMap<>();
			for (int i = 0; i < jobSpaceList.size(); i++) {
				AdmJobSpace jobSpace = jobSpaceList.get(i);
				if(jobSpace.getFloorId().equals(floorId)){
					jobMap.put(jobSpace.getSpaceId(), jobSpace);

					jobSpaceList.remove(i);
					i--;
				}
			}

			List<AdmObject> sps = entry.getValue();
			for(AdmObject sp : sps){
				ArrayNode spOutline = (ArrayNode) sp.getInfo("outline");
				AdmJobSpace job = jobMap.remove(sp.getId());
				if(job != null) {
					//id匹配，轮廓变化的空间，重置任务状态
					if(!outlineMatch(job.getOutline(), spOutline)){
						job.setArchState(AdmConst.JOB_STATE_PROCESSING);
						job.setEquipState(AdmConst.JOB_STATE_PROCESSING);
						job.setOutline(spOutline);
						toModify.add(job);
					} // else id和轮廓都匹配，不做处理
				} else {
					//id未匹配，轮廓完全一致的任务空间，重新设置spaceId
					job = matchByOutline(jobMap, sp);
					if(job != null) {
						job.setSpaceId(sp.getId());
						toModify.add(job);
					}
				}

				if (job == null) {
					job = new AdmJobSpace();
					job.setId(IdUtil.fastSimpleUUID());
					job.setSpaceId(sp.getId());
					job.setOutline((ArrayNode) sp.getInfo("outline"));
					job.setArchState(AdmConst.JOB_STATE_PROCESSING);
					job.setEquipState(AdmConst.JOB_STATE_PROCESSING);
					job.setProjectId(projectId);
					job.setBuildingId(sp.getBuildingId());
					job.setFloorId(sp.getFloorId());
					job.setCreationTime(time);
					job.setCreator(AdmConst.CREATOR_SYNC);
					job.setValid(ValidEnum.TRUE.getType());

					toCreate.add(job);
				}
			}

			//楼层中未找到匹配关系的任务空间
			for(AdmJobSpace job : jobMap.values()){
				job.setValid(ValidEnum.FALSE.getType());
				toModify.add(job);
			}
		}

		//项目下未找到匹配关系的任务空间
		for(AdmJobSpace job : jobSpaceList){
			job.setValid(ValidEnum.FALSE.getType());
			toModify.add(job);
		}

		for(AdmJobSpace job : toCreate) {
			jobSpaceMapper.insert(job);
		}
		for(AdmJobSpace job : toModify) {
			job.setModifiedTime(time);
			job.setModifier(AdmConst.CREATOR_SYNC);
			job.setUploadFlag(null);
			jobSpaceMapper.updateById(job);
		}
	}

	private AdmJobSpace matchByOutline(HashMap<String, AdmJobSpace> jobMap, AdmObject space){
		if(jobMap.size() == 0)
			return null;

		ArrayNode o1 = (ArrayNode) space.getInfo("outline");
		if(!isValidOutline(o1))
			return null;

		Polygon[] ps = GeomUtil.getPolygon(o1);
		Map.Entry<String, AdmJobSpace> target = null;
		for(Map.Entry<String, AdmJobSpace> entry : jobMap.entrySet()){
			ArrayNode o2 = entry.getValue().getOutline();
			if(isValidOutline(o2) && polygonsMatch(ps, GeomUtil.getPolygon(o2))){
				target = entry;
				break;
			}
		}
		if(target != null){
			jobMap.remove(target.getKey());
			return target.getValue();
		}
		return null;
	}

	/**
	 * 判断空间轮廓是否变化
	 * TODO 区分覆盖等情况
	 */
	private boolean outlineMatch(ArrayNode o1, ArrayNode o2){
		return polygonsMatch(GeomUtil.getPolygon(o1), GeomUtil.getPolygon(o2));
	}


	/**
	 * 轮廓匹配判断
	 * TODO 暂不考虑顺序
	 */
	private boolean polygonsMatch(Polygon[] ps1, Polygon[] ps2){
		if (ps1.length == ps2.length) {
			for (int i = 0; i < ps2.length; i++) {
				if (!ps1[i].equalsExact(ps2[i]))
					return false;
			}
			return true;
		}
		return false;
	}

	private boolean isValidOutline(ArrayNode outline) {
		if(outline != null && outline.size() > 0 && outline.get(0).isArray()) {
			ArrayNode o1 = (ArrayNode) outline.get(0);
			if (o1.size() > 0 && o1.get(0).isArray()) {
				ArrayNode o2 = (ArrayNode) o1.get(0);
				return o2.size() >= 3;
			}
		}
		return false;
	}

	@Autowired
	private OldAdmClient oldAdmClient;

	/**
	 *	把地图文件从image-service转存到文件服务
	 */
	@Override
	@Transactional(rollbackFor = Exception.class)
	public void uploadFloorMap(String groupCode, String projectId) {
		/* 不使用
		List<AdmObject> floors = objectService.queryM2dByObjType(projectId, AdmConst.OBJ_TYPE_FLOOR);
		List<AdmObject> modifiedFloors = new ArrayList<>();
		for(AdmObject floor : floors) {
			ObjectNode infos = floor.getInfos();
			if(infos != null) {
				JsonNode mapNode = infos.get("floorMap");
				JsonNode mapIdNode = infos.get("floorMapId");
				if(mapNode != null){
					String map = mapNode.asText();
					String mapId = mapIdNode != null ? mapIdNode.asText() : null;
					if(mapId != null) {
						FileInfo fileInfo = FileClientFacade.load(mapId);
						if(fileInfo != null && fileInfo.getFileName().equals(map))
							continue; //文件无变化

						//删除之前的地图文件
						FileStorageHelper.deleteFile(mapId);
					}

					try {
						Response serviceResponse = oldAdmClient.getFloorMap(map, "revit");
						Response.Body body = serviceResponse.body();

						InputStream inputStream = body.asInputStream();
						FileInfo fileInfo = FileInfoCreator.of(groupCode, projectId, AdmConst.APP_ID, map);
						String fileId = FileStorageHelper.uploadFile(fileInfo, inputStream);

						infos.put("floorMapId", fileId);
						modifiedFloors.add(floor);
					} catch (Exception e) {
						e.printStackTrace();
					}
				}
			}
		}

		objectService.update(modifiedFloors);
		*/
	}

}
