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

import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.Comparator;
import java.util.List;
import java.util.Map;
import java.util.function.Consumer;
import java.util.stream.Collectors;

import javax.annotation.Resource;

import org.springframework.stereotype.Component;

import cokr.xit.base.code.CommonCode;
import cokr.xit.base.user.DepartmentQuery;
import cokr.xit.base.user.SigunguQuery;
import cokr.xit.base.user.UserQuery;
import cokr.xit.base.user.service.bean.DepartmentBean;
import cokr.xit.base.user.service.bean.SigunguBean;
import cokr.xit.base.user.service.bean.UserBean;
import cokr.xit.fims.cmmn.CmmnQuery;
import cokr.xit.fims.cmmn.CmmnUtil;
import cokr.xit.fims.cmmn.CodeConverter;
import cokr.xit.fims.crdn.dao.CrdnTeamMapper;
import cokr.xit.fims.crdn.service.bean.CrdnStngBean;
import cokr.xit.fims.mngt.VltnQuery;
import cokr.xit.fims.mngt.dao.VltnMapper;
import cokr.xit.fims.mngt.service.bean.TaskProcessor;
import cokr.xit.fims.stat.CodeSubsetInfo;
import cokr.xit.fims.stat.CompositeKey;
import cokr.xit.fims.stat.GroupInfo;
import cokr.xit.fims.stat.NumberValueInfo;
import cokr.xit.fims.stat.Stat;
import cokr.xit.fims.stat.StatItem;
import cokr.xit.fims.stat.StatQuery;
import cokr.xit.foundation.component.AbstractBean;
import cokr.xit.foundation.data.DataObject;

@Component("statBean")
public class StatBean extends AbstractBean {
	@Resource(name = "crdnStngBean")
	private CrdnStngBean crdnStngBean;
	@Resource(name="sigunguBean")
	protected SigunguBean sigunguBean;
	@Resource(name = "departmentBean")
	private DepartmentBean departmentBean;
	@Resource(name="userBean")
	private UserBean userBean;
	@Resource(name="crdnTeamMapper")
	private CrdnTeamMapper teamMapper;
	@Resource(name="vltnMapper")
	private VltnMapper vltnMapper;

	/**쿼리에서 날짜와 관련된 파라미터를 설정한다.
	 * @param sql처리용 query, dayCol 날짜조회용 컬럼, from 조회시작일, to 조회종료일
	 * @return
	 */
	public void daySetting(CmmnQuery someQuery, String className, String dayCol, String from, String to) {
		try {
			if (dayCol.equals("REG_DT")) {
				Method m0 = CmmnQuery.class.getDeclaredMethod("setSchDateOpt", String.class);
				m0.invoke(someQuery, "regDt");
				Method m1 = CmmnQuery.class.getDeclaredMethod("setSchDateFrom", String.class);
				m1.invoke(someQuery, from);
				Method m2 = CmmnQuery.class.getDeclaredMethod("setSchDateTo", String.class);
				m2.invoke(someQuery, to);
			} else if (dayCol.equals("CRDN_YMD")) {
				Method m1 = someQuery.getClass().getDeclaredMethod("setSchCrdnYmdFrom", String.class);
				m1.invoke(someQuery, from);
				Method m2 = someQuery.getClass().getDeclaredMethod("setSchCrdnYmdTo", String.class);
				m2.invoke(someQuery, to);
			} else if (dayCol.equals("SNDNG_YMD")) {
				Method m1 = someQuery.getClass().getDeclaredMethod("setSchSndngYmdFrom", String.class);
				m1.invoke(someQuery, from);
				Method m2 = someQuery.getClass().getDeclaredMethod("setSchSndngYmdTo", String.class);
				m2.invoke(someQuery, to);
			} else if (dayCol.equals("RCPT_YMD")) {
				if (className.equals("LevyExclQuery")) {
					Method m1 = someQuery.getClass().getDeclaredMethod("setSchRcptYmdFrom", String.class);
					m1.invoke(someQuery, from);
					Method m2 = someQuery.getClass().getDeclaredMethod("setSchRcptYmdTo", String.class);
					m2.invoke(someQuery, to);
				}
			}
		} catch (Exception e) {
			throw runtimeException(e);
		}
	}

	/**통계요청에 따라 데이터목록에서 통계를 생성한다.
	 * @param queryResult SQL쿼리 처리 결과, statQuery 통계 요청
	 * @return 통계
	 */
	public Stat groupByCartegory(List<DataObject> queryResult, StatQuery statQuery) {
		//통계 항목별 그룹핑
		Map<CompositeKey, List<DataObject>> group = this.grouping(queryResult, statQuery);
		//고정 항목 설정
		if (statQuery.getFixedItemId() != null) {
			itemFix(group, statQuery);
		}

		//수치 값 추출
		List<StatItem> statItems = group.entrySet().stream()
			.map(entry -> {
				List<DataObject> listByKey = entry.getValue();

				StatItem statItem = new StatItem();
				statItem.setItemId(entry.getKey().toArray());
				int[] numberValues = extractNumberValue(listByKey, statQuery);
				statItem.setNumberValue(numberValues);

				if ("Y".equals(statQuery.getRefListYn())) {
					statItem.setRefList(listByKey);
				}

				return statItem;
			})
			.toList();

		Stat stat = new Stat();
		stat.setStatItems(statItems);
		return stat;
	}

	/**통계요청에 따라 데이터목록을 그룹핑한다.
	 * @param queryResult SQL쿼리 처리 결과, statQuery 통계 요청
	 * @return 통계 항목별로 그룹핑된 목록
	 */
	public Map<CompositeKey, List<DataObject>> grouping(List<DataObject> queryResult, StatQuery statQuery){
		List<GroupInfo> groupInfoList = statQuery.getGroupInfoList();
		List<CodeSubsetInfo> subsetInfoList = statQuery.getCodeSubsetInfoList();
		return queryResult.stream()
			.collect(
				Collectors.groupingBy((item) -> {
					CompositeKey compositeKey = new CompositeKey();
					List<Consumer<String>> setLevel = List.of(
						compositeKey::setLevel1,
						compositeKey::setLevel2,
						compositeKey::setLevel3,
						compositeKey::setLevel4
					);

					for (int i = 0; i < groupInfoList.size(); i++) {
						String key = "";
						GroupInfo groupInfo = groupInfoList.get(i);

						String refCol = groupInfo.getRefCol();
						String colValue = item.string(refCol);

						if (isEmpty(groupInfo.getCtgrType())) {
							key = ifEmpty(colValue, "(정보없음)");
							setLevel.get(i).accept(key);
							continue;
						}

						String ctgrType = groupInfo.getCtgrType();
						String addOption = groupInfo.getDtlCtgr();

						if ("date".equals(ctgrType)) {
							if (addOption.equals("YYYY")) { //연도별
								key = colValue.substring(0,4);
							} else if (addOption.equals("YYYYMM")) { //연월별
								key = colValue.substring(0,6);
							} else if (addOption.equals("YYYYMMDD")) { //일자별
								key = colValue.substring(0,8);
							} else if (addOption.equals("DAY_OF_WEEK")) { //요일별
								key = CmmnUtil.getDayOfWeek(colValue);
							} else if (addOption.equals("HH")) { //시간대별
								if (colValue.length() == 14) {
									key = colValue.substring(8,10);
								} else {
									key = colValue.substring(0,2);
								}
							} else {
								key = colValue;
							}

							key = ifEmpty(key, "(날짜없음)");
						} else if ("code".equals(ctgrType) || "otherCode".equals(ctgrType)) {
							if (ctgrType.equals("code") && !isEmpty(subsetInfoList)) {
								for (CodeSubsetInfo subsetInfo : subsetInfoList) {
									if (subsetInfo.getSourceGroup().equals(addOption)) {
										if (Arrays.asList(subsetInfo.getSourceCodes()).contains(colValue)) {
											colValue = subsetInfo.getTargetCode();
										}
									}
								}
							}
							key = ifEmpty(colValue, "(정보없음)");

						} else if ("method".equals(ctgrType)) {
							if (addOption.equals("businessYnOfCar")){
								String ffnlgCarmdlCd = item.string("FFNLG_CARMDL_CD");
								String vhrno = item.string("VHRNO");
								key = crdnStngBean.getBusinessYnOfCar(ffnlgCarmdlCd, vhrno);
							} else if (addOption.equals("carGb")){
								String vhrno = item.string("VHRNO");
								key = crdnStngBean.getCarGb(vhrno);

							} else {
								key = ifEmpty(colValue, "(정보없음)");
							}
						} else {
							key = ifEmpty(colValue, "(정보없음)");
						}
						setLevel.get(i).accept(key);
					}

					return compositeKey;
				}
			)
		);
	}

	/** 통계에 고정적으로 표시할 항목을 설정한다.
	 * @param group 그룹핑한 데이터 목록, statQuery 통계 요청;
	 * @return
	 */
	public void itemFix(Map<CompositeKey, List<DataObject>> group, StatQuery statQuery) {
		String[] fixedItemId = statQuery.getFixedItemId();
		CompositeKey[] fixedItemKey = new CompositeKey[fixedItemId.length];

		for (int i=0; i < fixedItemId.length; i++) {
			CompositeKey newObj = new CompositeKey();
			newObj.setLevel1(fixedItemId[i]);
			fixedItemKey[i] = newObj;
		}

		for (int i=0; i < fixedItemKey.length; i++) {
			if (!group.containsKey(fixedItemKey[i])) {
				group.put(fixedItemKey[i], null);
			}
		}

		List<CompositeKey> fixedItemKeys = Arrays.asList(fixedItemKey);
		List<CompositeKey> remove = group.keySet().stream()
			.filter(compositekey -> !fixedItemKeys.contains(compositekey))
			.toList();
		remove.forEach(group::remove);
	}

	/**통계요청에 따라 수치값(합계, 건수)을 계산한다.
	 * @param listByKey 특정 범주로 묶인 데이터 목록, statQuery 통계 요청
	 * @return 수치값
	 */
	public int[] extractNumberValue(List<DataObject> listByKey, StatQuery statQuery) {
		List<NumberValueInfo> numberValueInfoList = statQuery.getNumberValueInfoList();
		List<CodeSubsetInfo> subsetInfoList = statQuery.getCodeSubsetInfoList();

		int[] aggregateArr = new int[numberValueInfoList.size()];
		for (int i=0; i < numberValueInfoList.size(); i++) {
			aggregateArr[i] = 0;
		}

		if (listByKey != null) {
			for (int i=0; i<listByKey.size(); i++) {

				DataObject dataObject = listByKey.get(i);

				for (int j=0; j < aggregateArr.length; j++) {
					NumberValueInfo numverValueInfo = numberValueInfoList.get(j);

					String seperatorRefCol = ifEmpty(numverValueInfo.getSeperatorRefCol(), ()-> "");
					String conditionValue = ifEmpty(numverValueInfo.getConditionValue(), ()-> "");

					String seperatorCtgrType = ifEmpty(numverValueInfo.getSeperatorCtgrType(), ()-> "");
					String seperatorDtlCtgr = ifEmpty(numverValueInfo.getSeperatorDtlCtgr(), ()-> "");

					String aggregateType = ifEmpty(numverValueInfo.getAggregateType(), ()-> "");
					String sumRefCol = ifEmpty(numverValueInfo.getSumRefCol(), ()-> "");

					if (!seperatorRefCol.equals("")) {

						String dataColumnValue = dataObject.string(seperatorRefCol);

						if (seperatorCtgrType.equals("date")) {
							if (seperatorDtlCtgr.equals("YYYY")) { //연도별
								dataColumnValue = dataColumnValue.substring(0,4);
							} else if (seperatorDtlCtgr.equals("YYYYMM")) { //연월별
								dataColumnValue = dataColumnValue.substring(0,6);
							} else if (seperatorDtlCtgr.equals("YYYYMMDD")) { //일자별
								dataColumnValue = dataColumnValue.substring(0,8);
							} else if (seperatorDtlCtgr.equals("DAY_OF_WEEK")) { //요일별
								dataColumnValue = CmmnUtil.getDayOfWeek(dataColumnValue);
							} else if (seperatorDtlCtgr.equals("HH")) { //시간대별
								if (dataColumnValue.length() == 14) {
									dataColumnValue = dataColumnValue.substring(8,10);
								} else {
									dataColumnValue = dataColumnValue.substring(0,2);
								}
							}

						} else if (seperatorCtgrType.equals("code") || seperatorCtgrType.equals("otherCode")) {

							if (seperatorCtgrType.equals("code")
									&& subsetInfoList != null
									&& !subsetInfoList.isEmpty()
							) {

								for (CodeSubsetInfo subsetInfo : subsetInfoList) {
									if (subsetInfo.getSourceGroup().equals(seperatorDtlCtgr)) {
										if (Arrays.asList(subsetInfo.getSourceCodes()).contains(dataColumnValue)) {
											dataColumnValue = subsetInfo.getTargetCode();
										}
									}
								}
							}

						}

						if (!dataColumnValue.equals(conditionValue))
							continue;
					}

					if (aggregateType.equals("sum")) {
						aggregateArr[j] += dataObject.number(sumRefCol).intValue();
					} else {
						aggregateArr[j] += 1;
					}
				}
			}
		}
		return aggregateArr;
	}

	/** 특정 범주로 묶인 통계 항목에 항목명을 추가한다.
	 * @param statItems 통계 항목 목록, statQuery 통계 요청, allCode 코드 목록
	 * @return
	 */
	public void attachItemName(List<StatItem> statItems, StatQuery statQuery, Map<String, List<CommonCode>> allCode) {
		CodeConverter codeConverter = new CodeConverter(allCode);
		List<GroupInfo> groupInfoList = statQuery.getGroupInfoList();
		int taskSeCdIndex = -1;

		for (int i=0; i < groupInfoList.size(); i++) {
			if (groupInfoList.get(i).getCtgrType().equals("code")
			 && groupInfoList.get(i).getDtlCtgr().equals("FIM054")) {
				taskSeCdIndex = i;
			}
		}

		for (StatItem statItem : statItems) {
			String[] compositeItemId = statItem.getItemId();
			String[] compositeItemName = new String[compositeItemId.length];

			for (int i=0; i < compositeItemId.length ;i++) {
				GroupInfo groupInfo = groupInfoList.get(i);
				String ctgrType = blankIfEmpty(groupInfo.getCtgrType());
				String dtlCtgr = blankIfEmpty(groupInfo.getDtlCtgr());

				if (isEmpty(ctgrType) || ctgrType.equals("plain")
				 || ctgrType.equals("date") || ctgrType.equals("method")) { //일반텍스트
					compositeItemName[i] = compositeItemId[i];
					continue;
				}

				String itemName = "";
				if (ctgrType.equals("code")){ //공통코드
					String converted = codeConverter.codeToValue(dtlCtgr, compositeItemId[i]);
					itemName = ifEmpty(converted, compositeItemId[i]);
				} else if (ctgrType.equals("otherCode")) { //비공통코드
					if (dtlCtgr.equals("VLTN_CD")) {
						String taskSeCd = "";
						if (taskSeCdIndex != -1) {
							taskSeCd = compositeItemId[taskSeCdIndex];
						} else if (!isEmpty(statQuery.getTaskSeCd())) {
							taskSeCd = statQuery.getTaskSeCd();
						}

						if (isEmpty(taskSeCd))
							throw new RuntimeException("위반코드 변환 오류");

						String codeGroup = TaskProcessor.get().getVltnCdGrp(taskSeCd);
						String converted = codeConverter.vltnCdToValue(codeGroup, compositeItemId[i]);
						itemName = ifEmpty(converted, compositeItemId[i]);
					} else if (dtlCtgr.equals("VLTN_ID") || dtlCtgr.equals("TEAM_ID")
							|| dtlCtgr.equals("SGG_CD") || dtlCtgr.equals("DEPT_CD") || dtlCtgr.equals("USER_ID")) {

						String find = codeConverter.uniqOtherCodeToValue(dtlCtgr,compositeItemId[i]);
						if (!isEmpty(find)) {
							itemName = find;
						} else {
							DataObject findRow = this.selectOtherCode(dtlCtgr, compositeItemId[i]);

							if (findRow != null){
								itemName = findRow.string("NAME");
								codeConverter.putOtherCode(dtlCtgr, findRow);
							} else {
								itemName = compositeItemId[i];
							}
						}
					}
				}
				compositeItemName[i] = itemName;
			}
			statItem.setItemName(compositeItemName);
		}
	}


	/** 통계의 수치값 중 비율(%)을 계산한다.
	 * @param statItems 통계 항목 목록, statQuery 통계 요청
	 * @return
	 */
	public void attachRatioValue(List<StatItem> statItems, StatQuery statQuery) {
		int decimalPoint = statQuery.getDecimalPoint();
		List<NumberValueInfo> numberValueInfoList = statQuery.getNumberValueInfoList();

		for (StatItem statItem : statItems) {
			for (int i = 0; i < numberValueInfoList.size(); i++) {
				NumberValueInfo numberValueInfo = numberValueInfoList.get(i);
				String aggregateType = numberValueInfo.getAggregateType();

				if (aggregateType.equals("ratio")) {
					int refIndex = numberValueInfo.getRatioRefIndex();
					int totValue = statItem.getNumberValue()[refIndex];
					if (totValue == 0) {
						statItem.getNumberValue()[i] = 0;
					} else {
						int originalValue = statItem.getNumberValue()[i];
						double ratioValue = (double) originalValue / (double) totValue;
						double percent = ratioValue * Math.pow(10, 2f+decimalPoint);
						statItem.getNumberValue()[i] = (int) Math.floor(percent);
					}
				}
			}
		}
	}

	public DataObject selectOtherCode(String type, String uniqId) {
		switch(type) {
		case "SGG_CD" :
			List<DataObject> sggs = sigunguBean.getSigunguList(new SigunguQuery().setSggIDs(uniqId));
			if (!isEmpty(sggs)) {
				DataObject sgg = sggs.get(0);
				return sgg.set("NAME", sgg.string("SGG_NM"));
			}
			break;
		case "DEPT_CD" :
			List<DataObject> depts = departmentBean.getDepartmentList(new DepartmentQuery().setDeptIDs(uniqId));
			if (!isEmpty(depts)) {
				DataObject dept = depts.get(0);
				return dept.set("NAME", dept.string("DEPT_NM"));
			}
			break;
		case "VLTN_ID" :
			DataObject vltn = vltnMapper.selectVltnInfo(new VltnQuery().setVltnId(uniqId));
			if (!isEmpty(vltn)) {
				return vltn.set("NAME", vltn.string("VLTN_NM"));
			}
			break;
		case "USER_ID" :
			List<DataObject> users = userBean.getUserList(new UserQuery().setUserIDs(uniqId));
			if (!isEmpty(users)) {
				DataObject user = users.get(0);
				return user.set("NAME", user.string("USER_NM"));
			}
			break;
		case "TEAM_ID" :
			DataObject team = teamMapper.selectTeamInfo(uniqId);
			if (!isEmpty(team)) {
				return team.set("NAME", team.string("TEAM_NM"));
			}
			break;
		}
		return null;
	}

	/** 통계결과를 정렬한다.
	 * @param stat 통계
	 * @return
	 */
	public void sort(Stat stat) {
		List<StatItem> statItems = stat.getStatItems();
		if (isEmpty(statItems)) return;

		Comparator<StatItem> comparator = getStatItemComparator(statItems.get(0));
		statItems = statItems.stream().sorted(comparator).collect(Collectors.toList());
		stat.setStatItems(statItems);
	}

	public Comparator<StatItem> getStatItemComparator(StatItem sample){
		String[] itemIds = sample.getItemId();
		int level = itemIds.length;

		Comparator<StatItem> chainComparator = getStatItemNLevelComparator(1);
		if (level > 1) {
			for (int i=2; i <= level; i++) {
				chainComparator = chainComparator.thenComparing(getStatItemNLevelComparator(i));
			}
		}

		return chainComparator;
	}

	public Comparator<StatItem> getStatItemNLevelComparator(int level){
		return (statItemA, statItemB) -> {
			String[] statItemAIds = statItemA.getItemId();
			String[] statItemBIds = statItemB.getItemId();

			Comparator<String[]> tempComparator = getIndexOfStringArrayComparator(level-1);
			return tempComparator.compare(statItemAIds, statItemBIds);
		};
	}

	public Comparator<String[]> getStringArrayChainComparator(int lastIndex){
		Comparator<String[]> chainComparator = getIndexOfStringArrayComparator(0);

		if (lastIndex > 0) {
			for (int i=1; i <= lastIndex; i++) {
				chainComparator = chainComparator.thenComparing(getIndexOfStringArrayComparator(i));
			}
		}

		return chainComparator;
	}

	public Comparator<String[]> getIndexOfStringArrayComparator(int index) {
		return (arr1, arr2) -> {
			String str1 = arr1[index];
			String str2 = arr2[index];
			return str1.compareTo(str2);
		};
	}
}