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

import java.io.InputStream;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

import javax.annotation.Resource;

import org.springframework.stereotype.Component;

import cokr.xit.fims.FimsConf;
import cokr.xit.fims.cmmn.CrdnPayerAddrHstry;
import cokr.xit.fims.cmmn.CrdnSttsHstry;
import cokr.xit.fims.cmmn.service.bean.CrdnPayerAddrHstryBean;
import cokr.xit.fims.cmmn.service.bean.CrdnSttsHstryBean;
import cokr.xit.fims.crdn.Crdn;
import cokr.xit.fims.crdn.service.bean.CrdnBean;
import cokr.xit.fims.payer.Payer;
import cokr.xit.fims.payer.PayerQuery;
import cokr.xit.fims.payer.dao.PayerAddrMapper;
import cokr.xit.fims.payer.dao.PayerMapper;
import cokr.xit.foundation.component.AbstractBean;
import cokr.xit.foundation.data.DataObject;
import cokr.xit.interfaces.lvis.v2406.LvisA01;
import cokr.xit.interfaces.lvis.v2406.dao.VehicleInfoMapper;
import cokr.xit.interfaces.lvis.v2406.service.bean.VehicleInfoBean;

/**납부자 정보 관리 Bean
 *
 * <p>상세 설명:
 *
 * <pre>
 * ============ 변경 이력 ============
 * 2023-08-18	mjkhan 최초 작성
 * ================================
 * </pre>
 */
@Component("payerBean")
public class PayerBean extends AbstractBean {
	/** 납부자 정보 DAO */
	@Resource(name = "payerMapper")
	private PayerMapper payerMapper;
	/** 납부자 주소 정보 DAO */
	@Resource(name = "payerAddrMapper")
	private PayerAddrMapper payerAddrMapper;
	/** 자동차 정보 DAO */
	@Resource(name = "vehicleBean2406")
	private VehicleInfoBean vehicleInfoBean;
	@Resource(name = "vehicleMapper2406")
	private VehicleInfoMapper vehicleInfoMapper;
	/** 단속 상태 이력 정보 Bean */
	@Resource(name = "crdnSttsHstryBean")
	private CrdnSttsHstryBean crdnSttsHstryBean;
	/** 단속 관리 Bean */
	@Resource(name = "crdnBean")
	private CrdnBean crdnBean;
	/** 단속 납부자 이력 정보 Bean */
	@Resource(name = "crdnPayerAddrHstryBean")
	private CrdnPayerAddrHstryBean crdnPayerAddrHstryBean;

	/**지정한 조건에 따라 납부자 목록을 조회하여 반환한다.
	 * @param req 납부자 조회 조건
	 * @return 납부자 목록
	 */
	public List<DataObject> getPayerList(PayerQuery req) {
		return payerMapper.selectPayerList(req);
	}

	/**지정한 조건에 따라 납부자 객체들을 반환한다.
	 * @param req 납부자 조회 조건
	 * @return 납부자 객체 목록
	 */
	public List<Payer> getPayers(PayerQuery req) {
		return payerMapper.selectPayers(req);
	}

	/**지정한 조건에 따라 납부자 객체를 반환한다.
	 * @param req 납부자 조회 조건
	 * @return 납부자 객체
	 */
	public Payer getPayer(PayerQuery req) {
		if (req.getOrderBy() == null) {
			req.setOrderBy("P.RTPYR_ID DESC");
		}

        List<Payer> payers = getPayers(req);
        return !payers.isEmpty() ? payers.get(0) : null;
	}

	/**지정한 조건에 따라 납부자 주소 객체들을 반환한다.
	 * @param req 납부자 조회 조건
	 * @return 납부자 주소 객체 목록
	 */
	public List<Payer> getAddrs(PayerQuery req) {
		return payerAddrMapper.selectAddrs(req);
	}

	/**지정한 조건에 따라 납부자 주소 객체를 반환한다.
	 * @param req 납부자 조회 조건
	 * @return 납부자 주소 객체
	 */
	public Payer getPayerAddr(PayerQuery req) {
		if (req.getOrderBy() == null) {
			req.setOrderBy("PA.ADDR_SN DESC");
		}

		List<Payer> payers = getAddrs(req);
		return !payers.isEmpty() ? payers.get(0) : null;
	}

	public Payer getPayerAddrInfo(Payer payer) {
		// 최근 납부자 ID 설정
		PayerQuery req = new PayerQuery();
		req.setRtpyrSeCd(payer.getRtpyrSeCd());       // 납부자 구분 코드
		req.setRtpyrNo(payer.getRtpyrNo());           // 납부자 번호
		req.setAddrInptSeCd(payer.getAddrInptSeCd()); // 주소 입력 구분 코드
		req.setRoadNmCd(payer.getRoadNmCd());         // 도로명 코드
		req.setAddr(payer.getAddr());                 // 주소
		req.setDtlAddr(payer.getDtlAddr());           // 상세 주소
		req.setOrderBy("P.RTPYR_ID DESC");
		Payer last = getPayer(req);
		payer.setRtpyrId(last.getRtpyrId());

		// 최근 납부자 주소 일련번호 설정
		List<Payer> addrs = payerAddrMapper.selectPayerAddrInfo(
			req.setRtpyrId(payer.getRtpyrId())
			   .setOrderBy("PA.ADDR_SN DESC")
		);
        if (addrs.isEmpty()) {
            createPayerAddr(payer); // 주소 등록
		} else {
			last = addrs.get(0); // 최근 주소
            payer.setAddrSn(last.getAddrSn()); // 주소 일련번호
            updatePayerAddr(payer); // 주소 수정
        }

		return payer;
	}

	/**납부자 정보를 등록한다.
	 * @param payer 납부자
	 * @return 저장 여부
	 * <ul><li>저장됐으면 true</li>
	 *     <li>그렇지 않으면 false</li>
	 * </ul>
	 */
	public boolean create(Payer payer) {
		boolean created = payerMapper.insert(payer); // 납부자 등록
		createPayerAddr(payer, created);   // 주소 등록
		return created;
	}

	/**납부자 정보를 수정한다.
	 * @param payer 납부자
	 * @return 저장 여부
	 * <ul><li>저장됐으면 true</li>
	 *     <li>그렇지 않으면 false</li>
	 * </ul>
	 */
	public boolean update(Payer payer) {
		return payerMapper.update(payer);
	}

	/**납부자 주소 정보를 등록한다.
	 * @param payer 납부자
	 * @return 저장 여부
	 * <ul><li>저장됐으면 true</li>
	 *     <li>그렇지 않으면 false</li>
	 * </ul>
	 */
	public boolean createPayerAddr(Payer payer) {
		return createPayerAddr(payer, true);
	}

	private boolean createPayerAddr(Payer payer, boolean newPayer) {
		if (!newPayer) {
			PayerQuery req = new PayerQuery().setRtpyrSeCd(payer.getRtpyrSeCd());
			req.setRtpyrNo(payer.getRtpyrNo());
			Payer loaded = payerMapper.selectPayer(req);
			payer.setRtpyrId(loaded.getRtpyrId());
		}

		DataObject params = new DataObject()
			.set("roadNmCd", payer.getRoadNmCd())
			.set("udgdSeCd", payer.getUdgdSeCd())
			.set("bmno", payer.getBmno())
			.set("bsno", payer.getBsno());
		DataObject info = payerMapper.selectAddrInfo(params);
		if (info != null) {
			payer.setAddrSeCd("02");
			payer.setZip(info.string("ZIP"));
			payer.setBldgMngNo(info.string("BLDG_MNG_NO"));
			payer.setStdgCd(info.string("STDG_CD"));
			payer.setDongCd(info.string("DONG_CD"));
		}
		return payerAddrMapper.insertPayerAddr(payer) == 1;
	}

	/**납부자 주소 정보를 수정한다.
	 * @param payer 납부자
	 * @return 저장 여부
	 * <ul><li>저장됐으면 true</li>
	 *     <li>그렇지 않으면 false</li>
	 * </ul>
	 */
	public boolean updatePayerAddr(Payer payer) {
		return payerAddrMapper.updatePayerAddr(payer) == 1;
	}

	public Map<String, Payer> getVehicleOwners(LvisA01.A01Request req) {
		//차적조회
		LvisA01 bier = !FimsConf.get().isDisableLvis() ? vehicleInfoBean.getBasicInfo(req.distinct()) : null;
		List<LvisA01.BasicInfo> records = bier != null ? bier.getRecord() : Collections.emptyList();

		return records.stream()
			.filter(rec -> "00".equals(rec.getProcess_imprty_resn_code()))
			.map(rec -> getPayer(rec, req.getSigungu_code(), null))
			.collect(Collectors.toMap(
				payer -> payer.getVhrno(),
				payer -> payer,
				(arg0, arg1) -> arg0,
				LinkedHashMap::new
			));
	}

	public Payer getVehicleOwner(LvisA01.A01Request req) {
		Map<String, Payer> payers = getVehicleOwners(req);
		if (payers.isEmpty())
			return getPayer(null, req);

		return payers.values().stream().toList().get(0);
	}

	/**시군구 코드와 차량 기본정보에서 납부자 정보를 생성하거나, 등록정보에서 조회하여 반환한다.(차량번호 / 납부자 번호 / 시군구 기준)
	 * @param basicInfo 차량 기본정보
	 * @return 납부자 정보
	 */
	private Payer getPayer(LvisA01.BasicInfo basicInfo, LvisA01.A01Request req) {
		if (basicInfo == null) {
			basicInfo = new LvisA01.BasicInfo();
			basicInfo.setVhrno(req.getRecord().get(0).getVhrno());
			basicInfo.setProcess_imprty_resn_code("01");
			basicInfo.setProcess_imprty_resn_dtls("조회결과없음");
		}
		return getPayer(basicInfo, req.getSigungu_code(), null);
	}

	public List<DataObject> getPayers(LvisA01.A01Request req, InputStream upload) {
		List<DataObject> list = vehicleInfoBean.getBasicInfo(req, upload);
		for (DataObject row: list) {
			LvisA01.BasicInfo basicInfo = (LvisA01.BasicInfo)row.get("basicInfo");
			Payer payer = getPayer(basicInfo, req.getSigungu_code(), row.string("zipCode"));
			row.put("payer", payer);
		}
		return list;
	}

	private Payer getPayer(LvisA01.BasicInfo basicInfo, String sggCd, String zipCode) {
		Payer payer = new Payer();

		payer.setSggCd(sggCd);
		payer.setRtpyrInptSeCd("02");
		payer.setAddrInptSeCd("02");
		payer.setVhrno(basicInfo.getVhrno());

		payer.setVehicleInfo(basicInfo);

		payer.setRtpyrSeCd(
			switch (blankIfEmpty(basicInfo.getMber_se_code())) {
				case "11" -> "01";
				case "12" -> "02";
				case "13" -> "03";
				case "14" -> "04";
				case "15" -> "05";
				default -> "05";
			}
		);
		String str = basicInfo.getMber_se_no();
		payer.setRtpyrNo(str);
        if ("01,02".contains(payer.getRtpyrSeCd()))
            payer.setRtpyrBrdt(str.substring(0, 6));

		payer.setRtpyrNm(basicInfo.getMber_nm());
		String telno = basicInfo.getTelno();
		if (!isEmpty(telno) && telno.startsWith("010"))
			payer.setRtpyrMblTelno(telno);
		else
			payer.setRtpyrTelno(telno);

		//주소 세팅
		String use_strnghld_road_nm_code = basicInfo.getUse_strnghld_road_nm_code();
		payer.setRoadNmCd(use_strnghld_road_nm_code);
		payer.setUdgdSeCd(ifEmpty(basicInfo.getUsgsrhld_undgrnd_buld_se_code(), "0"));
		String use_strnghld_buld_main_no = basicInfo.getUse_strnghld_buld_main_no();
		if (!isEmpty(str = use_strnghld_buld_main_no))
			payer.setBmno(toInt(str));
		String use_strnghld_buld_sub_no = basicInfo.getUse_strnghld_buld_sub_no();
		if (!isEmpty(str = use_strnghld_buld_sub_no))
			payer.setBsno(toInt(str));
		payer.setStdgCd(basicInfo.getUse_strnghld_legaldong_code());
		payer.setDongCd(basicInfo.getUse_strnghld_adstrd_code());
		payer.setMtnSeCd(basicInfo.getUse_strnghld_mntn());
		if (!isEmpty(str = basicInfo.getUse_strnghld_lnbr()))
			payer.setMno(toInt(str));
		if (!isEmpty(str = basicInfo.getUse_strnghld_ho()))
			payer.setSno(toInt(str));

		if (!isEmpty(zipCode))
			payer.setZip(zipCode);
		if (!isEmpty(use_strnghld_road_nm_code)
		 && !isEmpty(use_strnghld_buld_main_no)
		 && !isEmpty(use_strnghld_buld_sub_no)
		) {
			DataObject roadNameInfo = vehicleInfoMapper.selectRoadNmAdres(
				use_strnghld_road_nm_code,
				use_strnghld_buld_main_no,
				use_strnghld_buld_sub_no
			);
			String road_nm_adres = roadNameInfo.string("ROAD_NM_ADRES");
			payer.setAddr(road_nm_adres);
			payer.setDtlAddr(str = basicInfo.getUse_strnghld_adres_nm());
			payer.setZip(roadNameInfo.string("ZIP"));
			// 전체 주소
			if (!isEmpty(str))
				payer.setWholAddr(road_nm_adres + ", " + str);
			else
				payer.setWholAddr(road_nm_adres);
		} else {
			String detailAddress = basicInfo.getUse_strnghld_adres_nm();
			String fullAddress = basicInfo.getUsgsrhld_adres_full();
			if (!isEmpty(detailAddress)) {
				String roadAddress = blankIfEmpty(fullAddress).replace(", " + detailAddress, "");
				payer.setAddr(roadAddress);
				payer.setDtlAddr(detailAddress);
				payer.setWholAddr(fullAddress);
			}
		}
		if (isEmpty(payer.getRtpyrNo()))
			return payer;

        Payer result = create(payer) ?
        	payer :
        	getPayerAddrInfo(payer);

        if (result != null)
			result.setVehicleInfo(basicInfo);

		return result;
	}

	/**지정한 조건에 따라 납부자 정보를 반환한다.
	 * @param req 납부자 조회 조건
	 * @return 납부자 정보
	 */
	public DataObject getCrdnPayerInfo(PayerQuery req) {
		return payerMapper.selectCrdnPayerInfo(req);
	}

	/**지정한 단속 ID의 단속 정보를 반환한다.<br />
	 * @param req 납부자 조회 조건
	 * @return 단속 정보
	 */
	public DataObject getCrdnInfo(PayerQuery req) {
		return payerMapper.selectCrdn(req);										// 단속 조회
	}

	/**납부자 정보를 등록하고, 단속 정보에 납부자 ID를 변경한다.
	 * @param payer 납부자
	 * @return 저장 여부
	 * <ul><li>저장됐으면 true</li>
	 *     <li>그렇지 않으면 false</li>
	 * </ul>
	 */
	public String createCrdnPayer(Payer payer) {
        if ("01,02".contains(payer.getRtpyrSeCd()))
            payer.setRtpyrBrdt(payer.getRtpyrNo().substring(0, 6)); // 생년월일
		if (isEmpty(payer.getUdgdSeCd()))
			payer.setUdgdSeCd("0"); // 지하 구분 코드
		if (isEmpty(payer.getWholAddr()))
			payer.setWholAddr(payer.getAddr() + ", " + payer.getDtlAddr()); // 전체 주소

		// 단속 ID로 단속, 납부자, 주소 정보 조회
		String crdnId = payer.getCrdnId();
		DataObject crdnPayerAddrInfo = payerMapper.selectCrdnInfo(crdnId);

		boolean bInstHstry = false;
		Payer result = null;
		// 주민등록번호가 동일하다면..
		if (crdnPayerAddrInfo != null && equals(crdnPayerAddrInfo.get("RTPYR_NO"), payer.getRtpyrNo())) {
			// 납부자 대장(TB_PAYER)에 납부자 정보를 수정한다.
			if (!update(payer))
				throw new RuntimeException("납부자 정보 수정에 실패하였습니다.");

            // 주소가 변경되었는지 확인
            if (equals(crdnPayerAddrInfo.get("ADDR_INPT_SE_CD"), payer.getAddrInptSeCd())
             && equals(crdnPayerAddrInfo.get("ZIP"), payer.getZip())
             && equals(crdnPayerAddrInfo.get("ADDR"), payer.getAddr())
             && equals(crdnPayerAddrInfo.get("DTL_ADDR"), payer.getDtlAddr())
             && equals(crdnPayerAddrInfo.get("ROAD_NM_CD"), payer.getRoadNmCd())
             && equals(crdnPayerAddrInfo.get("STDG_CD"), payer.getStdgCd()))       {
				// 주소 일련번호 설정
				payer.setAddrSn(crdnPayerAddrInfo.string("ADDR_SN"));

				// 납부자 번호와 주소가 같다면 단속 납부자주소 이력을 등록하지 않는다.
				bInstHstry = false;
			} else {
				result = getPayerAddrInfo(payer);
				// 납부자 번호가 같고, 주소가 다르다면 단속 납부자주소 이력을 등록한다.
                bInstHstry = true;                                              // 이력 등록 여부
			}
		} else {
			result = create(payer) ? payer : getPayerAddrInfo(payer);

        	// 단속 대장의 납부자ID 수정 및 단속 납부자 이력(TB_CRDN_PAYER_HSTRY) 대장에 등록한다.

            Payer updtPayer = new Payer();
            updtPayer.setRtpyrId("");
            updtPayer.setRtpyrNm(payer.getRtpyrNm());

            String str = result.getRtpyrTelno();           // 전화번호
            if (!isEmpty(str))
        	    updtPayer.setRtpyrTelno(str);
            if (!isEmpty(str = result.getRtpyrMblTelno())) // 휴대전화번호
                updtPayer.setRtpyrMblTelno(str);
            if (!isEmpty(str = result.getRtpyrEml()))      // 이메일
                updtPayer.setRtpyrEml(str);
            if (!isEmpty(str = result.getRtpyrSttsCd()))   // 납부자 상태 코드
                updtPayer.setRtpyrSttsCd(str);

            if (!isEmpty(str))
                updtPayer.setRtpyrId(result.getRtpyrId());

            // 납부자 대장(TB_PAYER)에 납부자 정보를 수정한다.
            if (!isEmpty(updtPayer.getRtpyrId())) {
                if (!update(updtPayer))
                    throw new RuntimeException("납부자 정보 수정에 실패하였습니다.");
            }

        	// 납부자 번호가 다르다면 단속 납부자주소 이력을 등록한다.
            bInstHstry = true;                                                  // 이력 등록 여부
		}

		// 단속 납부자 주소 이력(TB_CRDN_PAYER_ADDR_HSTRY) 대장에 등록한다.
		if (bInstHstry)
			createAddrHistory(result);

		Crdn crdn = crdnBean.getCrdn(crdnId);
		crdnBean.updateCrackdown(crdn);

		createCrdnSttsHistory(crdnPayerAddrInfo);

		return "[S] 작업이 정상 처리 되었습니다.";
	}

	public void createAddrHistory(Payer payer) {
		crdnPayerAddrHstryBean.createHstryUpdateCrdnPayerAddr(CrdnPayerAddrHstry.create(payer));
	}

	public void createCrdnSttsHistory(DataObject info) {
		if (toInt(info.get("CRDN_STTS_CD")) >= 21) return;

		// 납부자 등록 완료
		// 단속 상태 이력(TB_CRDN_STTS_HSTRY) 대장에 등록하고, 단속(TB_CRDN) 대장을 수정한다.
		CrdnSttsHstry crdnSttsHstry = CrdnSttsHstry.create("21", null, null, info);
		crdnSttsHstryBean.createHstryUpdateCrdnSttsCd(crdnSttsHstry);
	}

	/**지정한 조건에 따라 납부자 이력 목록을 조회하여 반환한다.
	 * @param req 납부자 조회 조건
	 * @return 납부자 변경 이력 목록
	 */
	public List<DataObject> getPayerHstrys(PayerQuery req) {
		if (req.getOrderBy() == null) {
			req.setOrderBy("CPH.REG_DT DESC");
		}

		return payerMapper.selectPayerHstrys(req);
	}

	/**지정한 조건에 따라 납부자 주소 이력 목록을 조회하여 반환한다.
	 * @param req 납부자 조회 조건
	 * @return 납부자 변경 이력 목록
	 */
	public List<DataObject> getPayerAddrHstrys(PayerQuery req) {
		if (req.getOrderBy() == null) {
			req.setOrderBy("CPAH.REG_DT DESC");
		}

		return payerAddrMapper.selectPayerAddrHstrys(req);
	}

    /**지정한 조건에 따라 납부자 주소 객체를 반환한다.
     * @param req 납부자 조회 조건
     * @return 납부자 주소 객체
     */
    public DataObject getLastPayerAddrInfo(PayerQuery req) {
        return payerAddrMapper.selectLastPayerAddrInfo(req);
    }
}