package cokr.xit.fims.crdn.service.bean;

import java.io.File;
import java.math.BigInteger;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.stream.Collectors;

import javax.annotation.Resource;

import org.springframework.stereotype.Service;

import cokr.xit.base.file.FileInfo;
import cokr.xit.base.file.service.bean.FileBean;
import cokr.xit.base.file.web.FileInfoFactory;
import cokr.xit.base.user.dao.UserMapper;
import cokr.xit.fims.FimsConf;
import cokr.xit.fims.cmmn.CmmnUtil;
import cokr.xit.fims.crdn.Crdn;
import cokr.xit.fims.crdn.Team;
import cokr.xit.fims.crdn.dao.CrdnTeamMapper;
import cokr.xit.fims.crdn.receive.eqpmnt.SingleFileParser;
import cokr.xit.fims.crdn.service.CrdnService;
import cokr.xit.fims.crdn.service.ImportService;
import cokr.xit.fims.mngt.service.bean.TaskProcessor;
import cokr.xit.foundation.component.AbstractServiceBean;
import cokr.xit.foundation.data.DataObject;
import cokr.xit.foundation.data.DataProc;
import cokr.xit.interfaces.smg.Petition;
import cokr.xit.interfaces.smg.service.bean.SmgReceiverBean;

/**외부 연계 파일에서 단속자료를 추출하여 등록하는 서비스의 구현체
 * @author mjkhan
 */
@Service("importService")
public class ImportServiceBean extends AbstractServiceBean implements ImportService {
	@Resource(name = "smgReceiverBean")
	private SmgReceiverBean smgBean;
	@Resource(name = "importBean")
	private ImportBean importBean;
	@Resource(name = "fileBean")
	private FileBean fileBean;
	@Resource(name = "crdnService")
	private CrdnService crdnService;
	@Resource(name = "crdnBean")
	private CrdnBean crdnBean;
	@Resource(name = "crdnStngBean")
	private CrdnStngBean crdnStngBean;

	@Resource(name="userMapper")
	protected UserMapper userMapper;
	@Resource(name = "crdnTeamMapper")
	private CrdnTeamMapper crdnTeamMapper;

	@Override
	public int createCrdns(List<String> interfaceSequences) {
		List<Petition> petitions = smgBean.getPetitions(interfaceSequences);
		List<Crdn> crdns = importBean.createCrdns(petitions);
		if (crdns.isEmpty()) return 0;

		List<String> intfSeqs = crdns.stream().map(Crdn::getCvlcptLinkId).toList();

		List<FileInfo> smgFileInfoList = fileBean.getFilesOf("010", intfSeqs.toArray(new String[intfSeqs.size()]));
		Map<String, List<FileInfo>> byInfoKey = smgFileInfoList.stream().collect(Collectors.groupingBy(FileInfo::getInfoKey));

		for (Crdn crdn: crdns) {
			List<FileInfo> files = byInfoKey.get(crdn.getCvlcptLinkId());

			files.removeIf(item -> item.getMimeType().startsWith("video"));

			for (FileInfo file : files) {
		    	file.setInputStream(file.getInputStream()); //신규 인풋스트림 생성
		    	file.setUrl(""); //인터페이스 첨부파일정보 제거
		    	file.setPath(""); //국민신문고 첨부파일정보 제거
			}

		    crdn.setCvlcptLinkYn("Y");
			crdnBean.create(null, crdn, files);
		}

		importBean.createCvlcpts(petitions);
		return 0;
	}

	@Override
	public Map<String, Object> createCrdnByEquipmentLinkFile(Map<String,String> processInfo, List<DataObject> fileList) {
		DataObject resp = new DataObject().set("received", fileList.size());
		String workPath = processInfo.get("workPath");
		String taskSeCd = processInfo.get("taskSeCd");
		String orgID = currentUser().getOrgID();
		String fileGroupType = processInfo.get("fileGroupType");
		boolean statByTeamEquipment = TaskProcessor.get().statByTeamEquipment(taskSeCd),
				excludeExempted = "true".equals(processInfo.get("excludeExempted")),
				cctv = isEmpty(workPath);

		List<DataObject> divided = divideFiles(fileList);
		resp.set("divided", divided.size());

		List<DataObject> errors = divided.stream()
			.filter(info -> info.get("metaInfo") == null)
			.distinct()
			.toList();
		divided.removeAll(errors); // 오류 항목 제외
		resp.set("normal", divided.size())
			.set("malformed", errors.size());

		setCrdnPlc(divided);

		List<DataObject> invalidPlace = divided.stream()
			.filter(info -> "invalid".equals(info.get("status")))
			.toList();
		divided.removeAll(invalidPlace); // 잘못된 단속장소 제외
		resp.set("invalidPlace", invalidPlace.size());

		Map<Crdn, DataObject> crdnMap = divided.stream()
			.map(dividedInfo -> {
				DataObject metaInfo = (DataObject)dividedInfo.get("metaInfo");
				DataObject result = new DataObject();

				List<DataObject> imageList = CmmnUtil.getDataObjectListFromMap(dividedInfo,"imageInfo");
				DataObject bgngData = (DataObject)dividedInfo.get("bgngData");
				DataObject endData = (DataObject)dividedInfo.get("endData");

				//1. 연계파일정보를 단속 엔티티로 변환
				Crdn crdn = new Crdn();
				crdn.setCrdnRegSeCd("07");
				crdn.setTaskSeCd(taskSeCd);
				crdn.setSggCd(orgID);

				crdnTimeRangeSet(crdn, bgngData, endData);
				crdn.setCrdnInptSeCd(
					switch (blankIfEmpty(metaInfo.get("LINK_ENT_NM"))) {
					case "ino" -> "11";
					case "knl" -> "12";
					default -> "15";
					}
				);
				crdn.setVltnCd(ifEmpty(metaInfo.string("VLTN_CD"), () -> "01"));
				crdn.setCrdnSpareaCd(ifEmpty(metaInfo.string("CRDN_SPAREA_CD"), () -> "00"));

				crdn.setVhrno(metaInfo.string("VHRNO"));
				crdn.setCrdnYmd(metaInfo.string("CRDN_YMD"));
				crdn.setCrdnTm(metaInfo.string("CRDN_TM"));
				if (isEmpty(crdn.getCrdnBgngTm())) {
					crdn.setCrdnBgngTm(metaInfo.string("CRDN_BGNG_TM"));
					crdn.setCrdnEndTm(metaInfo.string("CRDN_END_TM"));
				}

				crdn.setGpsX(metaInfo.string("GPS_X"));
				crdn.setGpsY(metaInfo.string("GPS_Y"));
				crdn.setEqpCd(metaInfo.string("EQPMNT_CD"));
				crdn.setCrdnPlc(metaInfo.string("CRDN_PLC"));
				crdn.setCrdnRoadNm(metaInfo.string("CRDN_ROAD_NM"));
				crdn.setCrdnStdgNm(metaInfo.string("CRDN_STDG_NM"));

				crdn.setCrdnSeCd(metaInfo.string("CRDN_SE_CD"));
				crdn.setMoscX(metaInfo.string("MOSC_X"));
				crdn.setMoscY(metaInfo.string("MOSC_Y"));

				if (statByTeamEquipment) {
					if (!isEmpty(crdn.getCrdnSeCd()) && !isEmpty(metaInfo.string("EQPMNT_CD"))) {
						Team team = new Team();
						team.setSggCd(crdn.getSggCd());
						team.setTaskSeCd(taskSeCd);
						team.setTeamNm(metaInfo.string("EQPMNT_CD"));
						DataObject teamInfo = crdnTeamMapper.selectTeamInfoByName(team);

						if (teamInfo != null && crdn.getCrdnSeCd().equals(teamInfo.string("CRDN_SE_CD"))) {
							crdn.setTeamId(teamInfo.string("TEAM_ID"));
						}
					}
				}
				//metaInfo.string("PLATE_WIDTH");
				//metaInfo.string("PLATE_HEIGHT");
				List<FileInfo> attachments = parseDataInfoToFileInfo(imageList, ifEmpty(fileGroupType, () -> metaInfo.string("FILE_GROUP_TYPE")));
				crdn.setAttachments(attachments);
				result
					.set("crdn", crdn)
					.set("files", cctv ? dividedInfo.get("files") : null);
				return result;
			})
			.collect(Collectors.toMap(
				info -> (Crdn)info.get("crdn"),
				info -> info,
				(org, rpl) -> org,
				LinkedHashMap::new
			));

		List<Crdn> crdns = crdnMap.keySet().stream().toList();
		//2. 차적 조회
		importBean.setVehicleInfo(crdns);

		//3.등록
		List<DataProc> dataProcs = crdnBean.create(crdns, excludeExempted);
		LinkedHashMap<String, List<File>> fileMap = new LinkedHashMap<>();
		String[] strs = {"success", "duplicates"};
		for (DataProc dataProc: dataProcs) {
			Crdn crdn = dataProc.getTarget();
			crdn.clearAttachments();
			DataObject info = crdnMap.get(crdn);
			List<DataObject> files = (List<DataObject>)info.get("files");
			if (isEmpty(files)) continue;

			List<File> extracted = files.stream()
				.map(file -> (File)file.get("file"))
				.distinct()
				.toList();

			for (String str: strs) {
				List<File> list = fileMap.get(str);
				if (list == null)
					fileMap.put(str, list = new ArrayList<>());
				if ("success".equals(str) && dataProc.isSuccess())
					list.addAll(extracted);
				else if ("duplicates".equals(str) && "duplicate".equals(dataProc.getInfo("reason"))) {
					list.addAll(extracted);
				}
			}
		}
		fileMap.put("fail", errors.stream().flatMap(info -> {
			List<DataObject> tmp = (List<DataObject>)info.get("files");
			return tmp.stream().map(file -> (File)file.get("file"));
		}).toList());
		resp.set("success", dataProcs.stream().filter(DataProc::isSuccess).count())
			.set("duplicates", dataProcs.stream().filter(dp -> "duplicate".equals(dp.getInfo("reason"))).count());

		if (!cctv)
			for (DataObject delInfo: fileList) {
				crdnBean.removeEquipmentLinkFile(workPath, delInfo.string("FILE_NAME"));
			}
		else {
			String dir = null;
			for (Map.Entry<String, List<File>> entry: fileMap.entrySet()) {
				String key = entry.getKey();
				List<File> files = entry.getValue();
				for (File file: files) {
					String srcDir = file.getParent(),
					       destDir = srcDir.replace(File.separator + "work", File.separator + key);
					if (!equals(dir, destDir))
						ensureDir(dir = destDir);
					String destPath = destDir + File.separator + file.getName();
					boolean moved = file.renameTo(new File(destPath));
					if (!moved)
						log().debug("Failed to move {} -> {}", file.getPath(), destPath);
/*
					try {
						Files.move(file.toPath(), Paths.get(destDir, file.getName()));
					} catch (Exception e) {
						throw runtimeException(e);
					}
*/
				}
			}
		}
		log().debug("process result: {}", resp);
		return resp;
	}

	/**파싱된 데이터 목록을 메타정보, 이미지정보목록, 최초단속자료, 최종단속자료로 분리한다. <br />
	 * @param fileList 연계파일정보목록, fileGroupType 연계파일그룹형식
	 * @return 메타정보, 이미지정보목록, 최초단속자료, 최종단속자료
	 */
	private List<DataObject> divideFiles(List<DataObject> fileList) {
		Map<String, List<DataObject>> byName = fileList.stream().collect(Collectors.groupingBy(fileInfo -> {
			String filename = fileInfo.string("FILE_NAME");
			return filename.substring(0, filename.lastIndexOf("_"));
		}));

		return byName.values().stream()
			.map(files -> {
				DataObject result = new DataObject();
				DataObject metaInfo = null;
				List<DataObject> imgs = null;
				DataObject[] bgngAndEnd = null;

				DataObject first = files.get(0);
				String groupType = first.string("FILE_GROUP_TYPE");

				if ("TXT".equalsIgnoreCase(groupType)) {
					Optional<DataObject> found = files.stream()
						.filter(file -> "TXT".equalsIgnoreCase(file.string("FILE_EXTENSION")))
						.findFirst();
					boolean present = found.isPresent();
					imgs = !present ? files : new ArrayList<>(files);
					if (present) {
						imgs.remove(metaInfo = found.get());
					}
					bgngAndEnd = getBgngDataAndEndData(imgs);
				} else if ("JPG".equalsIgnoreCase(groupType)) {
					bgngAndEnd = getBgngDataAndEndData(imgs = files);
					metaInfo = bgngAndEnd[1] == null ? first : bgngAndEnd[1];
				} else if ("BIN".equalsIgnoreCase(groupType)) {
					metaInfo = first;
					imgs = new ArrayList<>();
					for (int i = 1, otherPhotoCnt = toInt(metaInfo.get("PHOTO_CNT")); i <= otherPhotoCnt; i++){
						String fileName = metaInfo.string("B64IMAGE" + i + "_NM");
						String b64str = metaInfo.string("B64IMAGE" + i);
						DataObject dataObject = new DataObject()
							.set("B64IMAGE_NM", fileName)
							.set("B64IMAGE", b64str);
						imgs.add(dataObject);
					}
				}

				result
					.set("metaInfo", metaInfo)
					.set("imageInfo", imgs)
					.set("bgngData", bgngAndEnd[0])
					.set("endData", bgngAndEnd[1])
					.set("files", files);

				return result;
			})
			.distinct()
			.collect(Collectors.toCollection(ArrayList::new));
	}

	private void setCrdnPlc(List<DataObject> divided) {
		if (!FimsConf.get().isLookupEqpPlc()) return;

		List<DataObject> metaInfoList = divided.stream()
			.map(info -> (DataObject)info.get("metaInfo"))
			.toList();
		if (metaInfoList.isEmpty()) return;

		List<DataObject> eqpPlcList = Collections.emptyList(); // TODO: EquipmentPlace 완료되면 교체할 것
		if (eqpPlcList.isEmpty()) return;

		//키 = 장비코드-장소이름
		Map<String, DataObject> eqpPlcMap = eqpPlcList.stream().collect(Collectors.toMap(
			info -> info.get("EQP_CD") + "-" + info.get("PLC_NM"),
			info -> info
		));

		for (DataObject metaInfo: metaInfoList) {
			String key = metaInfo.get("EQPMNT_CD") + "-" + metaInfo.get("CRDN_PLC");
			DataObject eqpPlc = eqpPlcMap.get(key);
			if (eqpPlc == null) {
				metaInfo.set("status", "invalid");
				continue;
			}

			metaInfo
				.set("CRDN_STDG_NM", eqpPlc.get("STDG_NM"))
				.set("CRDN_ROAD_NM", eqpPlcMap.get("ROAD_NM"))
				.set("CRDN_PLC", eqpPlcMap.get("PLC"));
		}
	}

	/**단속이미지정보 목록 내에서 최초단속자료와 최종단속자료를 반환한다. 단속일시 정보가 없을 경우 null을 반환한다. <br />
	 * @param imageDataList 단속이미지정보 목록
	 * @return 최초단속자료, 최종단속자료
	 */
	private DataObject[] getBgngDataAndEndData(List<DataObject> imageDataList) {
		DataObject[] bgngAndEnd = {null, null};

		if (!isEmpty(imageDataList.get(0).get("CRDN_TIMESTAMP"))) {
			DataObject bgngData = imageDataList.stream()
	            .min(Comparator.comparing(item -> new BigInteger(item.string("CRDN_TIMESTAMP"))))
	            .orElseThrow();

			DataObject endData = imageDataList.stream()
			    .max(Comparator.comparing(item -> new BigInteger(item.string("CRDN_TIMESTAMP"))))
		        .orElseThrow();

			bgngAndEnd[0] = bgngData;
			bgngAndEnd[1] = endData;
		}

		return bgngAndEnd;
	}

	/**최초단속정보와 최종단속정보를 기준으로 단속대장의 최초단속시간과 최종단속시간을 설정한다. <br />
	 * @param crdn 단속대장, bgngData 최초단속자료, endData 최종단속자료
	 * @return
	 */
	private void crdnTimeRangeSet(Crdn crdn, DataObject bgngData, DataObject endData) {
		if (isEmpty(bgngData) || isEmpty(endData)) return;

		String crdnBgngTm = endData.string("CRDN_BGNG_TM");
		if (!isEmpty(crdnBgngTm)) {
			crdn.setCrdnBgngTm(crdnBgngTm);
			crdn.setCrdnEndTm(endData.string("CRDN_END_TM"));
		} else {
			String crdnTm = endData.string("CRDN_TM");
			if (!isEmpty(crdnTm)){
				crdn.setCrdnBgngTm(bgngData.string("CRDN_TM"));
				crdn.setCrdnEndTm(crdnTm);
			}
		}
	}

	/**단속이미지정보를 List<FileInfo>로 변환한다. <br />
	 * @param imageList 단속이미지정보 목록, fileGroupType 연계파일그룹형식
	 * @return fileInfo리스트
	 */
	private List<FileInfo> parseDataInfoToFileInfo(List<DataObject> imageList, String fileGroupType){
		try {
			List<FileInfo> fileInfos = null;
			if ("JPG,TXT".contains(fileGroupType)) {
				List<File> files = imageList.stream()
					.map(imageInfo -> {
						File img = (File)imageInfo.get("file");
						return ifEmpty(img, new File(imageInfo.string("FILE_PATH")));
					})
					.toList();

				fileInfos = new FileInfoFactory().createFileInfos(null, files);
				for (int i = 0; i < fileInfos.size(); i++) {
					FileInfo fileInfo = fileInfos.get(i);
					fileInfo.setName("image" + i + "." + fileInfo.getExtension());
				}
			} else if ("BIN".equals(fileGroupType)) {
				fileInfos = new ArrayList<>();
				for (int i = 0; i < imageList.size(); i++){
					String fileName = imageList.get(i).string("B64IMAGE_NM");
					String b64str = imageList.get(i).string("B64IMAGE");
					FileInfo FileInfo = SingleFileParser.base64ToFileInfo(fileName, b64str, (i + 1));
					fileInfos.add(FileInfo);
				}
			}
			return fileInfos;
		} catch (Exception e) {
			throw runtimeException(e);
		}
	};
}