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

import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.function.Supplier;
import java.util.stream.Collectors;

import javax.annotation.Resource;

import org.assertj.core.util.Arrays;

import cokr.xit.fims.cmmn.CmmnQuery;
import cokr.xit.fims.crdn.Crdn;
import cokr.xit.fims.crdn.CrdnQuery;
import cokr.xit.fims.crdn.dao.CrdnStngMapper;
import cokr.xit.fims.crdn.dao.ExmptnVhclMapper;
import cokr.xit.fims.excl.LevyExcl;
import cokr.xit.fims.mngt.dao.TaskMapper;
import cokr.xit.foundation.component.AbstractBean;
import cokr.xit.foundation.data.DataObject;

public class TaskProcessor extends AbstractBean {
	private static TaskProcessor proc;

	public static <T extends TaskProcessor> T get() {
		return (T)proc;
	}

	public static TaskProcessor get(Supplier<TaskProcessor> supplier) {
		return ifEmpty(proc, () -> proc = supplier.get());
	}

	protected DataObject vltnCdGrps = new DataObject()
		.set("PVS", "FIM004")
		.set("BPV", "FIM005")
		.set("DPV", "FIM006")
		.set("ECA", "FIM061")
		.set("PES", "FIM064")
		.set("DVS", "FIM066");

	protected Map<String, DataObject> tasks;

	@Resource(name="taskMapper")
	private TaskMapper taskMapper;
	@Resource(name = "crdnStngMapper")
	protected CrdnStngMapper crdnStngMapper;
	@Resource(name = "exmptnVhclMapper")
	private ExmptnVhclMapper exmptnVhclMapper;

	public DataObject getTaskInfo(String sggCd, String taskSeCd) {
		String key = "%s-%s";
		if (tasks == null) {
			List<DataObject> list = taskMapper.selectTasks(new CmmnQuery().setSggCd(sggCd));
			tasks = list.stream().collect(Collectors.toMap(
				row -> String.format(key, row.string("SGG_CD"), row.string("TASK_SE_CD")),
				row -> row
			));
		}
		return tasks.get(String.format(key, sggCd, taskSeCd));
	}

	public DataObject getTaskVltnCdGrps() {
		return vltnCdGrps.clone();
	}

	/**모든 위반내역 코드그룹을 반환한다.
	 * @return 모든 위반내역 코드그룹
	 */
	public List<String> getVltnCdGrps() {
		return vltnCdGrps.entrySet().stream()
			.map(entry -> (String)entry.getValue())
			.collect(Collectors.toCollection(ArrayList::new));
	}

	/**지정하는 업무의 위반내역 코드그룹을 반환한다.
	 * @param taskSeCd 업무코드
	 * @return 위반내역 코드그룹
	 */
	public String getVltnCdGrp(String taskSeCd) {
		return blankIfEmpty(vltnCdGrps.get(taskSeCd));
	}

	/**지정하는 업무의 기타 코드그룹을 반환한다.
	 * @param taskSeCd 업무코드
	 * @return 기타 코드그룹
	 */
	public String[] getExtraCdGrps(String taskSeCd) {
		return switch (taskSeCd) {
		case "PVS" -> new String[] {"FIM002", "FIM007", "FIM009"};
		case "BPV" -> new String[] {"FIM002", "FIM009"};
		case "DPV" -> new String[] {"FIM034"};
		default -> null;
		};
	}

	/**단속사진 정보에서 단속구분 코드를 반환한다.
	 * @param taskSeCd 업무구분 코드
	 * @param company 업체명
	 * @param info 단속사진 정보
	 * @return 단속구분 코드
	 */
	public String getCrdnSeCd(String taskSeCd, String company, DataObject info) {
		if ("PVS".equals(taskSeCd) || "BPV".equals(taskSeCd)) {
			if ("xit".equals(company))
				return !isEmpty(info.get("GPS_X")) ? "02" : "01"; // 01: 고정형 CCTV, 02: 주행형 CCTV
		}

		if ("BPV".equals(taskSeCd)) {
			if ("hitecom".equals(company))
				return "07"; // 07: 버스장착형 CCTV
		}
		return "01";
	}

	public List<Crdn> getDuplicates(List<Crdn> crdns) {
		if (isEmpty(crdns))
			return crdns;

		ArrayList<Crdn> copies = new ArrayList<>(crdns);
		Map<String, DataObject> selected = crdnStngMapper.selectDuplicateCrdns(copies).stream().collect(Collectors.toMap(
				info -> info.get("VHRNO") + "-" + info.get("CRDN_YMD"),
				info -> info
			));
		List<Crdn> duplicates = copies.stream()
			.filter(crdn -> {
				String key = crdn.getVhrno() + "-" + crdn.getCrdnYmd();
				DataObject info = selected.get(key);
				return info != null && toInt(info.get("CRDN_CNT")) > 0;
			})
			.collect(Collectors.toCollection(ArrayList::new));
		copies.removeAll(duplicates);

		copies.stream().collect(Collectors.groupingBy(
				crdn -> crdn.getVhrno() + "-" + crdn.getCrdnYmd())
			).forEach((str, items) -> {
				int size = items.size();
				if (size < 2) return;

				for (int i = 1; i < size; ++i) {
					Crdn item = items.get(i);
					if (!duplicates.contains(item))
						duplicates.add(item);
				}
			});

		duplicates.forEach(Crdn::clearAttachments);
		return duplicates;
	}

	public Map<Crdn, LevyExcl> getExcluded(List<Crdn> crdns) {
		List<Crdn> exemptedCrdns = getExemptedCrdns(crdns);
		return exemptedCrdns.stream()
			.collect(Collectors.toMap(
				crdn -> crdn,
				crdn -> {
					boolean created = isEmpty(crdn.getCrdnId()),
							exempted = exemptedCrdns.contains(crdn);
					String status = crdn.getCrdnSttsCd();
					if (created) {
						if ("83".equals(status)) // 계고, 계고
							return newLevyExcl(crdn, "2", "192");
						if (exempted) {
							crdn.setCrdnSttsCd("81");
							return newLevyExcl(crdn, "1", "145"); // 비부과, 면제차량
						} else
							return null;
					} else {
						if (!"81,83".contains(status) && exempted)
							return newLevyExcl(crdn, "1", "145"); // 비부과, 면제차량

						return null;
					}
				}
			));
	}

	protected List<Crdn> getExemptedCrdns(List<Crdn> crdns) {
		Map<String, DataObject> exempted = exmptnVhclMapper.selectExemptedCrdns(crdns).stream().collect(Collectors.toMap(
			info -> info.get("VHRNO") + "-" + info.get("CRDN_YMD"),
			info -> info
		));
		return crdns.stream().filter(crdn -> {
			String key = crdn.getVhrno() + "-" + crdn.getCrdnYmd();
			DataObject info = exempted.get(key);
			return info != null;
		})
		.collect(Collectors.toCollection(ArrayList::new));
	}

	/**단속 정보의 상태에 따라 부과제외 정보를 생성한다.
	 * @param crdn 단속 정보
	 * @return
	 * <ul><li>부과제외에 해당하면 부과제외 정보</li>
	 *     <li>해당하지 않으면 null</li>
	 * </ul>
	 */
	public LevyExcl getLevyExcl(Crdn crdn) {
		String crdnSttsCd = crdn.getCrdnSttsCd();
		if ("81".equals(crdnSttsCd)) { // 부과제외
			CrdnQuery req = new CrdnQuery()
				.setVhrno(crdn.getVhrno())
				.setCrdnYmd(crdn.getCrdnYmd())
				.setSggCd(crdn.getSggCd())
				.setTaskSeCd(crdn.getTaskSeCd());
			List<DataObject> list = exmptnVhclMapper.selectExemptionVehicleList(req);

			if (!list.isEmpty()) // 비부과, 면제차량
				return newLevyExcl(crdn, "1", "145");
			else
				throw new RuntimeException("비부과 사유를 알 수 없습니다.");
		}
		if ("83".equals(crdn.getCrdnSttsCd())) // 계고, 계고
			return newLevyExcl(crdn, "2", "192");
		return null;
	}

	/**주어진 구분과 사유로 부과제외 정보를 생성한다.
	 * @param crdn 단속 정보
	 * @param exclSeCd 부과제외 구분 코드
	 * @param exclRsnCd 부과제외 사유 코드
	 * @return 부과제외 정보
	 */
	public LevyExcl newLevyExcl(Crdn crdn, String exclSeCd, String exclRsnCd) {
		LevyExcl excl01 = new LevyExcl();
		excl01.setCrdnId(crdn.getCrdnId());
		excl01.setSggCd(crdn.getSggCd());
		excl01.setLevyExclYmd(dateFormats.today());
		excl01.setLevyExclSeCd(exclSeCd);
		excl01.setLevyExclRsnCd(exclRsnCd);
		excl01.setEtcCn("");
		return excl01;
	}

	/**중복자료 처리방법을 반환한다.
	 * @param taskSeCd 업무구분 코드
	 * @return 중복자료 처리방법
	 */
	public String getPrcsMthd(String taskSeCd) {
		return "PVS".equals(taskSeCd) ? "한건을 시간초과로 처리하십시요." : "한건을 서손으로 처리하십시요.";
	}

	/**주어진 업무의 감경 구분코드를 반환한다.
	 * @param taskSeCd 업무구분 코드
	 * @return 감경 구분코드
	 */
	public String getRdctSeCd(String taskSeCd) {
		return "BPV".equals(taskSeCd) ? "02" : "01"; // FIM046 - 02 미감경 01 감경
	}

	/**주어진 업무의 과태료과징금 구분코드를 반환한다.
	 * @param taskSeCd 업무구분 코드
	 * @return 과태료과징금 구분코드
	 */
	public String getFinScrgSeCd(String taskSeCd) {
		return "PES".equals(taskSeCd) ? "02" : "01"; // 01:과태료 02:과징금
	}

	/**주어진 단속정보의 과태료가 과징금인지 반환한다.
	 * @param crdn 단속정보
	 * @return 과징금 여부
	 */
	public boolean isPenalty(Crdn crdn) {
		if (!"PES".equals(crdn.getTaskSeCd()))
			return false;
		String ffnlgCarmdlCd = crdn.getFfnlgCarmdlCd();
		return "13".equals(ffnlgCarmdlCd)  // 화물4톤 이하
			|| "14".equals(ffnlgCarmdlCd); // 화물4톤 초과
	}

	protected static String[] SAFE_TAG_AREA = {"03","06","07"}; //안전표지구역

	protected static String[] CAR_GROUP1 = {"12","14","15","21"}; //승합자동차등
	protected static String[] CAR_GROUP2 = {"11","13"}; //승용자동차등
	protected static String[] CAR_GROUP3 = {"31"}; //이륜자동차등

	/**기본금액을 계산한다.
	 * @param crdn 단속 정보
	 * @return 최초금액, 사전통지금액
	 */
	public int[] getBasicAmt(Crdn crdn) {
		String taskSeCd = crdn.getTaskSeCd();
		String ffnlgCarmdlCd = crdn.getFfnlgCarmdlCd();
		int[] amt = {0, 0};

		if ("PVS,BPV,PES".contains(taskSeCd)) {
			if (isEmpty(ffnlgCarmdlCd))
				return amt;
		}

		String crdnYmd = crdn.getCrdnYmd();
		CrdnQuery query = new CrdnQuery()
			.setAplcnYmd(crdnYmd)
			.setTaskSeCd(taskSeCd);

		if ("PVS".equals(taskSeCd)) {
			query.setAmtDcsnCdGroupId("FIM055");
			String crdnSpareaCd = crdn.getCrdnSpareaCd();
			if (Arrays.asList(SAFE_TAG_AREA).contains(crdnSpareaCd)) {
				query.setAmtDcsnCd("02");
			} else {
				int crdnTm = toInt(crdn.getCrdnTm());
				boolean workTimeYn = crdnTm >= 80000 && crdnTm <= 200000;

				if (crdnSpareaCd.equals("01") && workTimeYn) {
					query.setAmtDcsnCd("03");
				} else if ((crdnSpareaCd.equals("02") || crdnSpareaCd.equals("05")) && workTimeYn) {
					query.setAmtDcsnCd("04");
				} else {
					query.setAmtDcsnCd("01");
				}
			}

			query.setDtlAmtDcsnGroupId("FIM056");
			if (Arrays.asList(CAR_GROUP1).contains(ffnlgCarmdlCd)) {
				query.setDtlAmtDcsnCd("01");
			}
			if (Arrays.asList(CAR_GROUP2).contains(ffnlgCarmdlCd)) {
				query.setDtlAmtDcsnCd("02");
			}

			int ffnlgCrdnAmt = toInt(crdnStngMapper.selectBasicAmt(query));
			if (ffnlgCrdnAmt > 0 && "Y".equals(crdn.getOvtmYn()))
				ffnlgCrdnAmt += 10000;

			amt[0] = ffnlgCrdnAmt;
			amt[1] = (int)(ffnlgCrdnAmt * 0.8);
		} else if ("BPV".equals(taskSeCd)) {
			query.setAmtDcsnCdGroupId("FIM056");
			if (Arrays.asList(CAR_GROUP1).contains(ffnlgCarmdlCd)) {
				query.setAmtDcsnCd("01");
			}
			if (Arrays.asList(CAR_GROUP2).contains(ffnlgCarmdlCd)) {
				query.setAmtDcsnCd("02");
			}
			if (Arrays.asList(CAR_GROUP3).contains(ffnlgCarmdlCd)) {
				query.setAmtDcsnCd("03");
			}

			amt[0] = amt[1] = toInt(crdnStngMapper.selectBasicAmt(query));
		} else if ("DPV".equals(taskSeCd)) {
			query.setAmtDcsnCdGroupId("FIM006");
			query.setAmtDcsnCd(crdn.getVltnCd());

			int ffnlgCrdnAmt = toInt(crdnStngMapper.selectBasicAmt(query));
			amt[0] = ffnlgCrdnAmt;
			amt[1] = (int)(ffnlgCrdnAmt * 0.8);
		} else if ("ECA".equals(taskSeCd)) {
			query.setAmtDcsnCdGroupId("FIM061");
			query.setAmtDcsnCd(crdn.getVltnCd());

			int ffnlgCrdnAmt = toInt(crdnStngMapper.selectBasicAmt(query));
			amt[0] = ffnlgCrdnAmt;
			amt[1] = (int)(ffnlgCrdnAmt * 0.8);
		} else if ("PES".equals(taskSeCd)) {
			query.setAmtDcsnCdGroupId("FIM057");

			if (ffnlgCarmdlCd.equals("21")) {
				query.setAmtDcsnCd("01");
				query.setDtlAmtDcsnGroupId("FIM059");

				String vltnNmtm = crdn.getVltnNmtm();
				if (isEmpty(vltnNmtm) || vltnNmtm.equals("0") || vltnNmtm.equals("1")) {
					query.setDtlAmtDcsnCd("01");
				} else if (vltnNmtm.equals("2")) {
					query.setDtlAmtDcsnCd("02");
				} else {
					query.setDtlAmtDcsnCd("03");
				}
			}

			if (ffnlgCarmdlCd.equals("13") || ffnlgCarmdlCd.equals("14")) {
				query.setAmtDcsnCd("02");
				query.setDtlAmtDcsnGroupId("FIM058");
				//query.setDtlAmtDcsnCd(업종);   //  TODO : 차주 업종
			}

			int ffnlgCrdnAmt = toInt(crdnStngMapper.selectBasicAmt(query));
			amt[0] = ffnlgCrdnAmt;
			boolean penalty = (ffnlgCarmdlCd.equals("13") || ffnlgCarmdlCd.equals("14")); //과징금
			amt[1] = penalty ? ffnlgCrdnAmt : (int)(ffnlgCrdnAmt * 0.8);
		}
		return amt;
	}

	/**금액을 변경할 수 있는지 반환한다.
	 * @param crdnInfo 단속정보
	 * @return 금액 변경 가능 여부
	 */
	public boolean isAmountChangeable(DataObject crdnInfo) {
		return "PVS".equals(crdnInfo.get("TASK_SE_CD")) && "Y".equals(crdnInfo.get("OVTM_PRTTN_YN"));
	}

	/**주어진 업무가 사전부과를 할 수 있는지 반환한다.
	 * @param taskSeCd 업무구분 코드
	 * @return 사전부과 여부
	 */
	public boolean isPreImposable(String taskSeCd) {
		return "BPV,PVS".contains(taskSeCd);
	}

	/**감경정보를 반영하여 부과본세를 반환한다.
	 * @param crdnRdctInfo 감경정보
	 * @return 부과본세
	 */
	public int getLevyPcptax(DataObject crdnRdctInfo) {
        int ffnlgAmt = toInt(crdnRdctInfo.get("ffnlgAmt")); // 과태료 금액
        int advntceAmt = toInt(crdnRdctInfo.get("advntceAmt")); // 사전통지 금액
        // 단속상태코드가 부과(51)면 과태료 금액, 부과 이전이라면 사전통지 금액으로..
        if (toInt(crdnRdctInfo.get("CRDN_STTS_CD")) == 51)
            return ffnlgAmt; // 부과 본세 = 과태료 금액

        String taskSeCd = crdnRdctInfo.string("TASK_SE_CD"); // 주정차위반, 전용차로위반 과태료
		if ("BPV,PVS".contains(taskSeCd)) {
            if (toInt(crdnRdctInfo.get("DUDT_YMD")) >= toInt(crdnRdctInfo.get("TODAY"))) { // 납기 일자 확인
                return advntceAmt; // 부과 본세 = 사전통지 금액
            } else {
                return ffnlgAmt;   // 부과 본세 = 과태료 금액
            }
        } else {
            return advntceAmt;     // 부과 본세 = 사전통지 금액
        }
	}

	public DataObject getMngItemCn(DataObject crdnInfo) {
		return switch (crdnInfo.string("TASK_SE_CD")) {
		case "PES" ->
			new DataObject()
				.set("item1", "차고지 외 밤샘주차")
				.set("item2", "위반일시 : " + crdnInfo.get("CRDN_YMD_TM_MASK"))
				.set("item3", "위반장소 : " + crdnInfo.string("CRDN_PLC"))
				.set("item4", "화물자동차운수사업법")
				.set("item5", switch (crdnInfo.string("CRDN_INPT_SE_CD")) {
					case "06" -> "관내단속";
					case "08" -> crdnInfo.string("ETC_CN");
					default -> "";
				});
		case "DPV", "ECA" ->
			new DataObject()
				.set("item1", "위반일시 : " + crdnInfo.string("CRDN_YMD_TM_MASK"))
				.set("item2", "")
				.set("item3", "위반장소 : " + crdnInfo.string("CRDN_PLC"))
				.set("item4", "")
				.set("item5", "위반항목 : " + crdnInfo.string("VLTN_ARTCL"));
		default -> throw new RuntimeException("지정되지 않은 업무 구분코드가 입력되었습니다.");
		};
	}

	/**주어진 업무의 발송구분에 해당하는 발송대장 내용문의 상세 종류 3번째 자리를 반환한다.
	 * @param taskSeCd 업무구분 코드
	 * @param sndngSeCd 발송구분 코드
	 * @return 발송대장 내용문 상세 종류 3번째 자리
	 */
	public String getSealCd3(String taskSeCd, String sndngSeCd) {
		if (sndngSeCd.equals("01"))
            return "E"; // E:과태료부과계고장

		return switch (taskSeCd) {
		case "DPV" -> "02".equals(sndngSeCd) ? "2" : "7"; // 2:장애인주차구역사전통보 7:장애인주차구역고지서
		case "ECA" -> "02".equals(sndngSeCd) ? "L" : "M"; // L:전기차충전구역과태료사전통지서 M:전기차충전구역과태료고지서
		default -> null;
		};
	}

	public DataObject getRecevDocCd(DataObject info) {
		if ("DPV,ECA".contains(info.string("taskSeCd"))) {
			String infoSndngSeNm = info.string("infoSndngSeNm");
			return new DataObject()
				.set("docCd1", "납부(" + infoSndngSeNm + ")서 영수증")                // 문서종류1
				.set("docCd2", info.get("taskSeNm") + " " + info.string("vltn")) // 문서종류2
				.set("docCd3", "[" + infoSndngSeNm + "]")                        // 문서종류3
				.set("docCd4", "[" + "시세외" + "]" + info.get("taxItem"))         // 문서종류4
				.set("docCd5", "영수필통지서");                                      // 문서종류5
		} else {
			return new DataObject()
				.set("docCd1", "")         // 문서종류1
				.set("docCd2", "")         // 문서종류2
				.set("docCd3", "")         // 문서종류3
				.set("docCd4", "")         // 문서종류4
				.set("docCd5", "영수필통지서"); // 문서종류5
		}
	}

	/**연계기관결제부서코드(RELORSECT_CD)를 반환한다.
	 * @param info 파라미터
	 * <ul><li>taskSeCd - 업무구분 코드</li>
	 *     <li>sndngDept - 발송부서 코드</li>
	 *     <li>deptCd - 부서 코드</li>
	 * </ul>
	 * @return 연계기관결제부서코드
	 */
	public String getRelorsectCd(DataObject info) {
		return "ECA".equals(info.get("taskSeCd")) && info.string("sndngDept").startsWith("420") ? "4200999" : info.string("deptCd");
	}

	/**지정하는 업무가 단속팀원/장비별 통계를 제공하는지 반환한다.
	 * @param taskSeCd 업무구분 코드
	 * @return 단속팀원/장비별 통계를 제공 여부
	 */
	public boolean statByTeamEquipment(String taskSeCd) {
		return "PVS,BPV".contains(taskSeCd);
	}

	/**주어진 업무의 세외수입 연계 대상을 반환한다.
	 * @param taskSeCd 업무구분 코드
	 * @return 세외수입 연계 대상
	 * <ul><li>서울 세외수입이면 sntris</li>
	 *     <li>지방 세외수입이면 lntris</li>
	 *     <li>연계하지 않으면 공백문자("")</li>
	 * </ul>
	 */
	public String getNtrisInterface(String taskSeCd) {
		return "lntris";
	}

	/**주어진 업무의 우편발송 서비스 이름을 반환한다.
	 * @param taskSeCd 업무구분 코드
	 * @return 우편발송 서비스 이름(디폴트는 eGreen)
	 */
	public String getNoticeDelivery(String taskSeCd) {
		return "eGreen";
	}

	/**자동차정보 시스템 접속 설정 이름을 반환한다.
	 * @param taskSeCd 업무구분 코드
	 * @return 자동차정보 시스템 접속 설정 이름
	 */
	public String getLvisConfig(String taskSeCd) {
		return "default";
	}
}