package cokr.xit.fims.crdn.web;

import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardCopyOption;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Stream;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.servlet.ModelAndView;

import cokr.xit.base.code.CommonCode;
import cokr.xit.base.file.FileInfo;
import cokr.xit.base.file.FileInspector;
import cokr.xit.base.file.web.FileInfoFactory;
import cokr.xit.base.user.dao.UserMapper;
import cokr.xit.base.web.ApplicationController;
import cokr.xit.fims.FimsConf;
import cokr.xit.fims.base.FimsUser;
import cokr.xit.fims.base.service.bean.OgdpBean;
import cokr.xit.fims.cmmn.CmmnUtil;
import cokr.xit.fims.cmmn.ftp.FTPUtil;
import cokr.xit.fims.cmmn.ftp.RemoteSystemInfo;
import cokr.xit.fims.cmmn.service.bean.StngBean;
import cokr.xit.fims.crdn.Crdn;
import cokr.xit.fims.crdn.CrdnQuery;
import cokr.xit.fims.crdn.dao.CrdnInstMapper;
import cokr.xit.fims.crdn.dao.CrdnStngMapper;
import cokr.xit.fims.crdn.receive.eqpmnt.AttachedTxtParser;
import cokr.xit.fims.crdn.receive.eqpmnt.LayoutDescriptor;
import cokr.xit.fims.crdn.receive.eqpmnt.LayoutDiscriminator;
import cokr.xit.fims.crdn.receive.eqpmnt.LayoutParser;
import cokr.xit.fims.crdn.receive.eqpmnt.OnlyImageParser;
import cokr.xit.fims.crdn.receive.eqpmnt.SingleFileParser;
import cokr.xit.fims.crdn.receive.eqpmnt.dao.CrdnEqpmntMapper;
import cokr.xit.fims.crdn.receive.eqpmnt.service.bean.CrdnEqpmntBean;
import cokr.xit.fims.crdn.service.CrdnService;
import cokr.xit.fims.crdn.service.CrdnStngService;
import cokr.xit.fims.crdn.service.ImportService;
import cokr.xit.fims.crdn.service.bean.CrdnStngBean;
import cokr.xit.fims.mngt.service.bean.TaskProcessor;
import cokr.xit.fims.task.Task;
import cokr.xit.foundation.UserInfo;
import cokr.xit.foundation.data.DataObject;

/**단속 자료 등록 서비스 웹 컨트롤러.<br />
 * {웹 컨텍스트}/crdn/crdn05 로 접근할 수 있다.
 * @author leebj
 */
@Controller
@RequestMapping(name="단속 자료 등록", value=Crdn05Controller.CLASS_URL)
public class Crdn05Controller extends ApplicationController {
	public static final String CLASS_URL = "/crdn/crdn05";

	public class METHOD_URL {
		public static final String
		crackdownDataRegistrationMain 	= "/010/main.do",
		getTodayCrdnDataList 			= "/010/list.do",

		getFileRegistrationScreen 		= "/020/info.do",
		importFileFromServer 			= "/020/importFileFromServer.do",
		importFileFromClient 			= "/020/importFileFromClient.do",
		getEquipmentFileInfoList 		= "/020/list.do",
		removeLinkFile 					= "/020/remove.do",
		createCrdnByLinkFile 			= "/020/create.do",

		getManualRegistrationScreen 	= "/030/info.do",
		createCrdnDataByManual 			= "/030/create.do"
		;
	}

	@Resource(name="importService")
	private ImportService importService;
	@Resource(name="crdnService")
	private CrdnService crdnService;
	@Resource(name="crdnStngService")
	private CrdnStngService crdnStngService;
	@Resource(name="crdnStngBean")
	private CrdnStngBean crdnStngBean;
	@Resource(name="crdnInstMapper")
	private CrdnInstMapper crdnInstMapper;
	@Resource(name="userMapper")
	protected UserMapper userMapper;
	@Resource(name="crdnStngMapper")
	private CrdnStngMapper crdnStngMapper;
	@Resource(name = "ogdpBean")
	private OgdpBean ogdpBean;
	@Resource(name = "stngBean")
	private StngBean stngBean;
	@Resource(name = "crdnEqpmntBean")
	private CrdnEqpmntBean crdnEqpmntBean;
	@Resource(name = "crdnEqpmntMapper")
	private CrdnEqpmntMapper crdnEqpmntMapper;

	/** 단속 자료 등록 메인화면을 연다.
	 * @return fims/crdn/crdn05010-main
	 */
	@RequestMapping(name="단속 자료 등록 메인", value=METHOD_URL.crackdownDataRegistrationMain)
	public ModelAndView crackdownDataRegistrationMain() {
		Map<String, List<CommonCode>> commonCodes = getCodesOf("FIM003", "FIM026", "FIM054");

		return addCodes(
			commonCodes,
			new ModelAndView("fims/crdn/crdn05010-main")
				.addObject("pageName", "crdn05010")
				.addObject("FIM054List", commonCodes.get("FIM054"))
				.addObject("TaskListForSgg", stngBean.filterTaskSectionCodeForSgg(commonCodes.get("FIM054"))),
			"FIM003", "FIM026"
		);
	}

	/**당일 등록한 단속자료 목록을 조회한다.
	 * {@link CrdnService#getCrackdownList(CrdnQuery)} 참고
	 * @param query 단속자료 목록 조회 조건
	 * @return jsonView
	 */
	@Task
	@RequestMapping(name="당일 입력 단속자료 목록 조회", value=METHOD_URL.getTodayCrdnDataList)
	public ModelAndView getTodayCrdnDataList(CrdnQuery query) {
		UserInfo currentUser = currentUser();
		setFetchSize(query)
			.setGridType("todayInsert")
			.setCurrentUserId(currentUser.getId())
			.setSggCd(currentUser.getOrgID());

		return setPagingInfo(new ModelAndView("jsonView"), crdnService.getCrackdownList(query), "");
	}

	/**단속자료 파일 등록 팝업화면을 반환한다.
	 * @param
	 * @return fims/crdn/crdn05020-info
	 */
	@Task
	@RequestMapping(name="단속자료 파일 등록 화면", value=METHOD_URL.getFileRegistrationScreen)
	public ModelAndView getFileRegistrationScreen(HttpServletRequest hreq) {
		Map<String, List<CommonCode>> commonCodes = getCodesOf("FIM054");

		List<DataObject> dirs = new FileInspector().getDirList(FimsConf.get().getViolationFileDir());
		dirs.sort((r1, r2) -> r2.string("name").compareTo(r1.string("name")));

		return new ModelAndView("fims/crdn/crdn05020-info")
			.addObject("pageName", "crdn05020")
			.addObject("taskSeCd", hreq.getParameter("taskSeCd"))
			.addObject("FIM054List", commonCodes.get("FIM054"))
			.addObject("TaskListForSgg", stngBean.filterTaskSectionCodeForSgg(commonCodes.get("FIM054")))
			.addObject("dirList", toJson(dirs));
	}

	@RequestMapping(name="cctv 단속파일 목록", value="/020/files.do")
	public ModelAndView getDirFiles(String dir) {
		List<DataObject> fileList = new FileInspector().getFileList(FimsConf.get().violationFileDir(dir));

		return new ModelAndView("jsonView")
			.addObject("fileList", fileList);
	}

	/** FTP 서버로부터 단속연계파일을 가져온다.
	 * @param
	 * @return
	 */
	@Task
	@RequestMapping(name="단속연계파일 가져오기(FTP)", value=METHOD_URL.importFileFromServer)
	public ModelAndView importFileFromServer(HttpServletRequest hreq) {
		FimsUser currentUser = (FimsUser) currentUser().getUser();
		List<LayoutDescriptor> layoutDescriptors = crdnStngService.getLinkFileLayoutMetadata(currentUser.getOrgID(), hreq.getParameter("taskSeCd"), currentUser().getInstitute(), currentUser.getDeptCode());
		ModelAndView mav = new ModelAndView("jsonView");
		for(int i=0; i < layoutDescriptors.size(); i++) {
			LayoutDescriptor layoutDescriptor = layoutDescriptors.get(i);
			DataObject remoteInfo = crdnEqpmntMapper.selectRemoteInfo(layoutDescriptor.getFileLayoutId());
			if (remoteInfo == null || isEmpty(remoteInfo.string("REMOTE_IP")))
				continue;

			RemoteSystemInfo rs = new RemoteSystemInfo();
			rs.setIp(remoteInfo.string("REMOTE_IP"));
			rs.setPort(remoteInfo.string("REMOTE_PORT"));
			rs.setId(remoteInfo.string("REMOTE_ID"));
			rs.setPw(remoteInfo.string("REMOTE_PASSWORD"));
			rs.setOsType(remoteInfo.string("REMOTE_OS"));

			String remoteWorkPath = remoteInfo.string("REMOTE_WORK_PATH");
			String workPath = layoutDescriptor.getLinkFileLocation();
			ensureDir(workPath);

			try {
		        boolean result = FTPUtil.fileDown(rs, remoteWorkPath, workPath);
		        if (!result) {
		        	return mav.addObject("saved", false);
		        }
			} catch (Exception e) {
				return mav.addObject("saved", false);
			}
			CmmnUtil.removeDuplicateFileName(workPath);
		}

		return mav.addObject("saved", true);
	}

	/** 사용자 클라이언트(브라우저)로부터 단속연계파일을 가져온다.
	 * @param
	 * @return
	 */
	@Task
	@RequestMapping(name="단속연계파일 가져오기(파일업로드)", value=METHOD_URL.importFileFromClient)
	public ModelAndView importFileFromClient(HttpServletRequest hreq, MultipartFile[] uploadFiles) {
		UserInfo currentUser = currentUser();
		List<LayoutDescriptor> layoutDescriptors = crdnStngService.getLinkFileLayoutMetadata(
			currentUser.getOrgID(),
			hreq.getParameter("taskSeCd"),
			currentUser.getInstitute(),
			currentUser.getDeptCode()
		);

		LayoutDescriptor layoutDescriptor =
			layoutDescriptors.size() == 1 ? layoutDescriptors.get(0) :
			layoutDescriptors.size() > 1 ? LayoutDiscriminator.choice(uploadFiles, layoutDescriptors) :
			new LayoutDescriptor();

		String workPath = layoutDescriptor.getLinkFileLocation();
		ensureDir(workPath);

		ModelAndView mav = new ModelAndView("jsonView");
		try {
			for (MultipartFile upload: uploadFiles) {
				File newFile = new File(workPath+File.separator+upload.getOriginalFilename());
				upload.transferTo(newFile);
			}
		} catch (Exception e) {
			return mav.addObject("saved", false);
		}

		CmmnUtil.removeDuplicateFileName(workPath);

		return mav.addObject("saved", true);
	}

	/**전송,업로드 완료된 장비업체의 단속연계파일 정보를 조회한다.
	 * @param
	 * @return
	 */
	@Task
	@RequestMapping(name="장비업체 단속파일 목록 조회", value=METHOD_URL.getEquipmentFileInfoList)
	public ModelAndView getEquipmentFileInfoList(HttpServletRequest hreq) {
		UserInfo currentUser = currentUser();
		List<LayoutDescriptor> layoutDescriptors = crdnStngService.getLinkFileLayoutMetadata(
			currentUser.getOrgID(),
			hreq.getParameter("taskSeCd"),
			currentUser.getInstitute(),
			currentUser.getDeptCode()
		);
		if (isEmpty(layoutDescriptors))
			throw new RuntimeException("연계파일 레이아웃 정보 조회에 실패하였습니다.");

		int nextTempGroupSeq = 1;
		List<DataObject> allParsedDataList = new ArrayList<DataObject>();
		TaskProcessor taskProcessor = TaskProcessor.get();
		Map<String, List<CommonCode>> codeInfo = getCodesOf("FIM004","FIM005","FIM006","FIM007","FIM061");
		for(LayoutDescriptor layoutDescriptor: layoutDescriptors) {
			String workPath = layoutDescriptor.getLinkFileLocation();
			ensureDir(workPath);

			List<Path> fileList = null;
			try { //폴더는 제외하고 파일만 필터링
				fileList = Files.walk(Paths.get(workPath)).filter(Files::isRegularFile).toList();
	        } catch (IOException e) {
	        	throw new RuntimeException("파일 조회 오류." + e);
	        }
			if (isEmpty(fileList)) continue;

			LayoutParser parser = switch (layoutDescriptor.getFileGroup()){
				case "TXT" -> new AttachedTxtParser();
				case "JPG" -> new OnlyImageParser();
				case "BIN" -> new SingleFileParser();
				default -> null;
			};
			parser.setTaskProcessor(taskProcessor);
			parser.setTempGroupSeq(nextTempGroupSeq);
			parser.addCommonCode(codeInfo);
			parser.setDescriptor(layoutDescriptor);

			List<DataObject> parsedDataList = parser.parsing(fileList);
			allParsedDataList.addAll(parsedDataList);
			nextTempGroupSeq = parser.getTempGroupSeq() + 1;
		}

		return setPagingInfo(new ModelAndView("jsonView"), allParsedDataList,"");
	}

	/**단속 연계 파일을 삭제한다.
	 * @param hreq 삭제 요청
	 * @return jsonView
	 * <pre><code> {
	 *     "saved": 등록되었으면 true, 그렇지 않으면 false
	 * }</code></pre>
	 */
	@Task
	@RequestMapping(name="장비업체 단속파일 삭제", value=METHOD_URL.removeLinkFile)
	public ModelAndView removeLinkFile(HttpServletRequest hreq) {
		LayoutDescriptor info = crdnEqpmntBean.getLinkFileLayoutMetadata(hreq.getParameter("fileLayoutId"));

		String workPath = info.getLinkFileLocation();
		String fileName = hreq.getParameter("fileName");
		String rtnMsg = crdnService.removeEquipmentLinkFile(workPath, fileName);

		return new ModelAndView("jsonView")
			.addObject("saved", rtnMsg.contains("[S]"));
	}

	/**단속 연계 파일로 단속자료를 생성한다.
	 * @param taskSeCd 업무 구분, fileGroupType 파일그룹타입, linkFileInfos 연계파일정보
	 * @return jsonView
	 * <pre><code> {
	 *     "saved": 등록되었으면 true, 그렇지 않으면 false
	 * }</code></pre>
	 */
	@Task
	@RequestMapping(name="장비업체 단속파일로 단속자료 생성", value=METHOD_URL.createCrdnByLinkFile)
	public ModelAndView createCrdnByLinkFile(String taskSeCd, String fileLayoutId, String fileGroupType, String[] linkFileInfos) {
		Map<String, String> processInfo = new HashMap<String,String>();
		processInfo.put("institute", currentUser().getInstitute());
		processInfo.put("taskSeCd", taskSeCd);
		processInfo.put("fileGroupType", fileGroupType);

		String workPath = crdnEqpmntBean.getLinkFileLayoutMetadata(fileLayoutId).getLinkFileLocation();
		processInfo.put("workPath", workPath);

		List<DataObject> linkFileInfoList = Stream.of(linkFileInfos)
			.filter(linkFileInfo -> !linkFileInfo.equals("{}"))
			.map(linkFileInfo -> fromJson(linkFileInfo, DataObject.class))
			.toList();
		Map<String, Object> result = importService.createCrdnByEquipmentLinkFile(processInfo, linkFileInfoList);
		return new ModelAndView("jsonView")
			.addAllObjects(result);
/*
		boolean saved = (boolean) result.get("saved");

		ModelAndView mav = new ModelAndView("jsonView")
			.addObject("saved", saved);
		if (!isEmpty(result.get("alertMessage")))
			mav.addObject("alertMessage", result.get("alertMessage"));

		if (!saved)
			mav.addObject("failReason", ifEmpty(result.get("failReason"), () -> "알 수 없는 오류"));

		return mav;
*/
	}

	@PostMapping(name="장비업체 단속파일로 단속자료 생성", value="/020/upload.do")
	public ModelAndView createCrdnFromLinkFile(HttpServletRequest hreq, MultipartFile[] uploadFiles) {
		UserInfo currentUser = currentUser();
		String sggCd = currentUser.getOrgID(),
			   taskSeCd = hreq.getParameter("taskSeCd"),
			   instCd = currentUser.getInstitute(),
			   deptCode = currentUser.getDeptCode();

		List<LayoutDescriptor> descriptors = crdnStngService.getLinkFileLayoutMetadata(sggCd, taskSeCd, instCd, deptCode);
		if (isEmpty(descriptors))
			throw new RuntimeException("연계파일 레이아웃 정보 조회에 실패하였습니다.");

		int nextTempGroupSeq = 1;
		TaskProcessor taskProcessor = TaskProcessor.get();
		HashMap<String, String> processInfo = new HashMap<>();
		processInfo.put("institute", currentUser().getInstitute());
		processInfo.put("taskSeCd", taskSeCd);
		processInfo.put("excludeExempted", hreq.getParameter("doExempt"));

		Map<String, LayoutParser> parsers = Map.of(
			"TXT", new AttachedTxtParser(),
			"JPG", new OnlyImageParser(),
			"BIN", new SingleFileParser()
		);
		Map<String, List<CommonCode>> codeInfo = getCodesOf("FIM004","FIM005","FIM006","FIM007","FIM061");

		List<DataObject> parsed = new ArrayList<DataObject>();
		for (LayoutDescriptor descriptor: descriptors) {
			LayoutParser parser = parsers.get(descriptor.getFileGroup());
			parser.setTaskProcessor(taskProcessor);
			parser.setTempGroupSeq(nextTempGroupSeq);
			parser.addCommonCode(codeInfo);
			parser.setDescriptor(descriptor);

			if (!isEmpty(uploadFiles)) { // 사용자 pc 파일 업로드
				String workPath = crdnEqpmntBean.getLinkFileLayoutMetadata(descriptor.getFileLayoutId()).getLinkFileLocation();
				processInfo.put("workPath", workPath);
				parsed.addAll(parser.parse(workPath, List.of(uploadFiles)));
			} else { // 서버 cctv 디렉토리
				String[] dirs = blankIfEmpty(hreq.getParameter("dirs")).split(",");
				for (String dir: dirs) {
					String srcDir = FimsConf.get().violationFileDir(dir);
					List<String> filenames = List.of(blankIfEmpty(hreq.getParameter("filenames")).split(","));
					log().debug("{} filenames received", filenames.size());
					Path workDir = ensureDir(srcDir + File.separator + "work").toPath();
					try {
						// 파일들을 작업 디렉토리로 이동
						List<Path> filepaths = Files.list(Paths.get(srcDir))
							.filter(path -> contains(path.toString(), filenames, false))
							.toList();
						log().debug("{} filepaths extracted", filepaths.size());
						filepaths.forEach(src -> {
							try {
								Files.move(src, workDir.resolve(src.getFileName()), StandardCopyOption.REPLACE_EXISTING);
							} catch (Exception e) {
								throw runtimeException(e);
							}
						});

						List<File> files = Files.list(workDir)
							.filter(path -> contains(path.toString(), filenames, true))
							.map(path -> path.toFile())
							.toList();
						log().debug("{} files parsed", files.size());
						parsed.addAll(parser.parse(files));
					} catch (Exception e) {
						throw runtimeException(e);
					}
				}
			}
			nextTempGroupSeq = parser.getTempGroupSeq();
		}

		Map<String, Object> result = importService.createCrdnByEquipmentLinkFile(processInfo, parsed);
		return new ModelAndView("jsonView")
			.addAllObjects(result);
	}

	private boolean contains(String path, List<String> filenames, boolean inWork) {
		if (!inWork) {
			if (path.contains("work")
			 || path.contains("success")
			 || path.contains("fail")
			 || path.contains("duplicates")
			 ) return false;
		}

		for (String filename: filenames) {
			if (path.endsWith(filename))
				return true;
		}
		return false;
	}

	/**단속자료 수기 등록 팝업화면을 반환한다.
	 * @param
	 * @return fims/crdn/crdn05030-info
	 */
	@Task
	@RequestMapping(name="단속자료 수기 등록 화면", value=METHOD_URL.getManualRegistrationScreen)
	public ModelAndView getManualRegistrationScreen(HttpServletRequest hreq) {
		String taskSeCd = hreq.getParameter("taskSeCd");
		String sggCd = currentUser().getOrgID();
		List<String> stdgNmList = crdnStngMapper.selectStdgNmList(sggCd);

		ModelAndView mav = new ModelAndView("fims/crdn/crdn05030-info")
			.addObject("pageName", "crdn05030")
			.addObject("taskSeCd", taskSeCd)
			.addObject("sggCd", sggCd)
			.addObject("stdgNmList", stdgNmList)
			.addObject("today", dateFormats.format("yyyyMMdd", System.currentTimeMillis()));

		TaskProcessor taskProcessor = TaskProcessor.get();
		String vltnByTask = taskProcessor.getVltnCdGrp(taskSeCd);
		Map<String, List<CommonCode>> commonCodes = getCodesOf("FIM011","FIM053","LVS005",vltnByTask);
		mav.addObject("FIM011List", commonCodes.get("FIM011"))
		   .addObject("FIM053List", commonCodes.get("FIM053"))
		   .addObject("LVS005List", commonCodes.get("LVS005"))
		   .addObject("VLTNList", commonCodes.get(vltnByTask));

		CrdnQuery query = new CrdnQuery()
			.setSggCd(sggCd)
			.setTaskSeCd(taskSeCd);
		List<DataObject> teamList = crdnStngService.getTeamList(query);

		String[] extraCodeGroups = taskProcessor.getExtraCdGrps(taskSeCd);
		if (!isEmpty(extraCodeGroups)) {
			commonCodes = getCodesOf(extraCodeGroups);
			commonCodes.forEach((codeGroup, codes) -> {
				mav.addObject(codeGroup + "List", codes);
			});
		}
		return mav.addObject("TeamList", teamList);
	}

	/**단속 대장을 수기 등록한다.
	 * @param hreq 등록 요청, crdn 단속 대장 정보, newFileList 업로드한 이미지 파일
	 * @return jsonView
	 * <pre><code> {
	 *     "saved": 등록되었으면 true, 그렇지 않으면 false
	 * }</code></pre>
	 */
	@Task
	@PostMapping(name="단속자료 수기 등록", value=METHOD_URL.createCrdnDataByManual)
	public ModelAndView createCrdnDataByManual(HttpServletRequest hreq, Crdn crdn, MultipartFile[] newFileList) {
		UserInfo userInfo = currentUser();
		ogdpBean.initUserInfo(userInfo);

		crdn.setSggCd((String)userInfo.getInfo().get("sggCd"));
		crdn.setCrdnRegSeCd("01");
		crdn.setCrdnInptSeCd(((String)userInfo.getInfo().get("instNm")).endsWith("시") ? "03" : "02"); // 03: 시청, 02: 구청

		if ("on".equals(hreq.getParameter("doWarning"))) // 계고 처리
			crdn.setCrdnSttsCd("83");
		if ("on".equals(hreq.getParameter("doExempt"))) // 면제차량
			crdn.setCrdnSttsCd("81");

		Map<String, Object> nonQueryRequest = new HashMap<String, Object>();
		crdn.setCvlcptLinkYn("N");

		List<FileInfo> fileInfoList = !isEmpty(newFileList) ? new FileInfoFactory().makeFileInfos(null, newFileList) : Collections.emptyList();
		String rtnMsg = crdnService.create(nonQueryRequest, crdn, fileInfoList);

		return new ModelAndView("jsonView")
			.addObject("saved", rtnMsg.contains("[S]"));
	}
}