package kr.xit.biz.ens.service;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;

import javax.validation.ConstraintViolation;
import javax.validation.Validation;
import javax.validation.Validator;

import org.apache.commons.lang3.StringUtils;
import org.slf4j.MDC;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.HttpMethod;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

import KISINFO.VNO.VNOInterop;
import egovframework.com.cmm.util.EgovDateUtil;
import egovframework.com.cmm.util.EgovStringUtil;
import kr.xit.biz.common.ApiConstants;
import kr.xit.biz.common.ApiConstants.MappingCanSndngProcessStatus;
import kr.xit.biz.common.ApiConstants.MappingCcnSndngProcessStatus;
import kr.xit.biz.common.ApiConstants.SndngProcessStatus;
import kr.xit.biz.common.ApiConstants.SndngSeCode;
import kr.xit.biz.ens.cmm.CmmEnsBizUtils;
import kr.xit.biz.ens.mapper.IEnsBatchMapper;
import kr.xit.biz.ens.model.EnsDTO;
import kr.xit.biz.ens.model.cmm.SndngMssageParam;
import kr.xit.biz.ens.model.nice.NiceCiDTO;
import kr.xit.core.exception.BizRuntimeException;
import kr.xit.core.service.AbstractService;
import kr.xit.core.spring.util.ApiWebClientUtil;
import kr.xit.core.spring.util.MessageUtil;
import kr.xit.core.support.utils.Checks;
import kr.xit.core.support.utils.DateUtils;
import lombok.NonNull;
import lombok.RequiredArgsConstructor;

/**
 * <pre>
 * description : 모바일 전자고지 배치 서비스
 *               - 배치에서 호출되는 클래스로 배치 로그등 Transaction 분리 필요
 *               - 메소드 트랜잭션 Transactional(propagation = Propagation.REQUIRES_NEW)로 선언
 * packageName : kr.xit.biz.ens.service
 * fileName    : EnsBatchService
 * author      : limju
 * date        : 2023-08-31
 * ======================================================================
 * 변경일         변경자        변경 내용
 * ----------------------------------------------------------------------
 * 2023-08-31    limju       최초 생성
 *
 * </pre>
 */
@RequiredArgsConstructor
@Service
public class EnsBatchAcceptService extends AbstractService implements IEnsBatchAcceptService {
    @Value("${app.contract.host}")
    private String apiHost;

    @Value("${app.contract.nice.api.ci}")
    private String apiNiceCi;

    @Value("${app.contract.nice.dici.site-code}")
    private String diciSiteCode;

    @Value("${app.contract.nice.dici.site-pwd}")
    private String diciSitePwd;

    private final IEnsBatchMapper mapper;
    private final MessageUtil messageUtil;

    private static final Validator validator = Validation.buildDefaultValidatorFactory()
        .getValidator();

    private static final String UNITY_SNDNG_MST_ID = "unitySndngMastrId";
    private static final String YMDHMS = ApiConstants.FMT_DT_EMPTY_DLT;

    /**
     * 춘천 시스템 여부
     */
    private final ApiWebClientUtil apiWebClient;

    /**
     * <pre>
     * accept(접수) 처리
     * - 연계발송마스터의 발송처리상태가 'accept' 인 데이타
     * - 처리 완료 : 통합발송마스터, 디테일 생성
     *             - tb_ens_unity_sndng_mastr
     *             - tb_ens_unity_sndng_detail
     *             연계발송마스터의 발송처리상태 'accept-ok'
     * - 실패 : 연계발송마스터의 발송처리상태 'accept-fail'
     *
     * 1. 발송 접수 대상 조회 : 연계발송마스터의 발송처리상태가 접수(accept)인 데이타
     *    -> {@link IEnsBatchMapper#selectAcceptTgts selectAcceptTgts}
     *    - tb_cntc_sndng_mastr, tb_ens_tmplat_manage
     *
     * 2. 접수 대상 파라메터 validation : {@link #validatedAccept}
     *    -> 파라메터 오류시 오류 처리 : 발송처리상태 접수실패(accept-fail)
     *       {@link #updateAcceptCntcSndngMstFailStatus}
     *       - tb_cntc_sndng_mastr
     *    -> 오류 처리후 다음 데이터로 진행
     *
     * 3. 접수
     *    3-1. 통합발송마스터 생성 : 발송처리상태 접수로
     *         {@link IEnsBatchMapper#insertUnitySndngMst insertUnitySndngMst}
     *         - tb_ens_unity_sndng_mastr
     *    3-2. 통합발송상세 생성
     *         {@link IEnsBatchMapper#insertUnitySndngDtls insertUnitySndngDtls}
     *         - tb_ens_unity_sndng_detail
     *    3-3. 연계발송마스터 상태 변경 : 발송처리상태가 접수완료(accept-ok)
     *         {@link IEnsBatchMapper#updateProcessSttusCntcSndngMst updateProcessSttusCntcSndngMst}
     *         - tb_cntc_sndng_mastr
     * @param reqDTO BatchEnsRequest
     * </pre>
     */
    @Override
    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public void accept(final EnsDTO.BatchEnsRequest reqDTO) {
        if(ApiConstants.IS_CCN) reqDTO.setSndngProcessSttus(MappingCcnSndngProcessStatus.fromEns(SndngProcessStatus.ACCEPT.getCode()));
        else                    reqDTO.setSndngProcessSttus(MappingCanSndngProcessStatus.fromEns(SndngProcessStatus.ACCEPT.getCode()));
        final List<SndngMssageParam> list = mapper.selectAcceptTgts(reqDTO);
        String sndngDt = null;

        for (SndngMssageParam dto : list) {
            //try {
                dto.setSignguCode(reqDTO.getSignguCode());
                dto.setFfnlgCode(reqDTO.getFfnlgCode());
                dto.setProfile(ApiConstants.IS_PROFILE_LOCAL? "local" : "prod");
                final List<String> mstIdList = new ArrayList<>();
                String mstId = "";
                if (!Objects.equals(mstId, dto.getUnitySndngMastrId())) {
                    mstId = dto.getUnitySndngMastrId();
                    mstIdList.add(mstId);
                    MDC.put(UNITY_SNDNG_MST_ID, mstId);
                }

                // validation check
                final List<EnsDTO.SndngValidation> valiList = mapper.selectAcceptValidation(dto);
                try {
                    // CI 생성이 모두 실패한 경우 오류처리후 skip
                    if(!validatedAccept(valiList)){
                        updateAcceptCntcSndngMstFailStatus(mstIdList,
                            "accept 생성 요청 실패 : api log 확인");
                        continue;
                    }
                } catch (Exception e) {
                    updateAcceptCntcSndngMstFailStatus(mstIdList,
                        "accept 생성 요청 실패(파라메터 오류) : " + EgovStringUtil.cutString(e.getMessage(), 300));
                    continue;
                }

                // 초기 1회 셋팅 (발송 일시2, 발송 일시3 :  템플릿 관리 try2_minute, try3_minute 값에 따라 처리)
                if (StringUtils.isEmpty(sndngDt)) {
                    sndngDt = StringUtils.defaultString(dto.getSndngDt());
                    if (StringUtils.isNotEmpty(dto.getTry2()) && StringUtils.isNotEmpty(sndngDt)) {
                        dto.setSndngDt2(
                            EgovDateUtil.addMinute(sndngDt, Integer.parseInt(dto.getTry2Minute())));
                    }
                    if (StringUtils.isNotEmpty(dto.getTry3()) && StringUtils.isNotEmpty(sndngDt)) {
                        dto.setSndngDt3(
                            EgovDateUtil.addMinute(sndngDt, Integer.parseInt(dto.getTry3Minute())));
                    }
                }

                dto.setSndngProcessSttus(reqDTO.getSndngProcessSttus());
                dto.setNewSndngProcessSttus(SndngProcessStatus.ACCEPT_OK.getCode());
                if (mapper.insertUnitySndngMst(dto) != 1) {
                    throw BizRuntimeException.create("접수 데이타 생성(마스터) 실패");
                }
                int insCnt = mapper.insertUnitySndngDtls(dto);
                if (insCnt != dto.getSndngCo()) {
                    throw BizRuntimeException.create(
                        String.format("접수 상세 데이타 생성 실패-발송건수[%d]와 생성건수[%d] 불일치",
                            dto.getSndngCo(),
                            insCnt));
                }

                CmmEnsBizUtils.setMappingProcessStatus(dto, SndngProcessStatus.ACCEPT_OK.getCode());
                if (mapper.updateProcessSttusCntcSndngMst(dto) != 1) {
                    throw BizRuntimeException.create("접수 데이타 생성(통합발송마스터 상태변경) 실패");
                }
            // } catch (Exception e) {
            //     // FIXME: 에러 로그 저장 추가 필요
            //     //throw e;
            //     // updateAcceptCntcSndngMstFailStatus(mstIdList,
            //     //     "accept 생성 요청 실패(파라메터 오류) : " + EgovStringUtil.cutString(e.getMessage(), 300));
            //     continue;
            // }
        }
    }

    //*************************************************************************
    // private method
    //*************************************************************************
    /**
     * <pre>
     * accept 파라메터 유효성 체크
     *
     * @param valiList List<EnsDTO.sndngVali>
     * </pre>
     */
    // FIXME:파라메터 유효성 check 는 하지않는다???
    private boolean validatedAccept(List<EnsDTO.SndngValidation> valiList) {
        final String url = apiHost + apiNiceCi;
        final List<String> errors = new ArrayList<>();
        int idx = 0;
        boolean isSuccess = false;

        for (EnsDTO.SndngValidation dto : valiList) {
            Set<ConstraintViolation<EnsDTO.SndngValidation>> errList = validator.validate(dto);

            if(!errList.isEmpty()) {
                int finalIdx = idx;
                errors.addAll(errList.stream()
                    .map(row -> String.format("%s[%d]=%s", row.getPropertyPath(), finalIdx + 1,
                        row.getMessageTemplate()))
                    .toList()
                );
            }

            // 발송일시보다 마감일시가 빠르면 안되고, 마감일시가 현재 시간보다 빠르면 오류
            if(!Checks.isEmpty(dto.getSndngDt()) && !Checks.isEmpty(dto.getClosDt())) {
                if(!DateUtils.isBeforeLocalDateTime(dto.getSndngDt(), dto.getClosDt(),
                    YMDHMS)) {
                    errors.add(String.format("발송일시보다 마감일시가 빠를 수 없습니다(dto.getSndngDt[%d] 번째 오류)",
                        idx + 1));
                }
                if(!DateUtils.isBeforeLocalDateTime(DateUtils.getTodayAndNowTime(YMDHMS),
                    dto.getClosDt(), YMDHMS)) {
                    errors.add(
                        String.format("현재 시간보다 마감일시가 빠를 수 없습니다(dto.getSndngDt[%d] 번째 오류)",
                            idx + 1));
                }
            }

            final SndngSeCode sndngSeCode = getSndngSeCodFromTrySeq(dto);

            // CI 생성 여부
            boolean isCiCreate = false;
            boolean isError = false;
            switch(sndngSeCode) {
                case KAKAO -> {
                    if (Checks.isEmpty(dto.getCi())) {
                        isCiCreate = true;

                        if (Checks.isEmpty(dto.getIhidnum())) {
                            isError = true;
                            errors.add(
                                String.format("주민등록번호는 필수입니다(dto.getIhidnum[%d] 번째 오류)",
                                    idx + 1));
                        }
                        /*if (Checks.isEmpty(dto.getMoblphonNo())) {
                            isError = true;
                            errors.add(
                                String.format("핸드폰 번호는 필수입니다(dto.getMoblphonNo[%d] 번째 오류)",
                                    idx + 1));
                        }
                        if (Checks.isEmpty(dto.getIhidnum())) {
                            isError = true;
                            errors.add(
                                String.format("받는이 이름은 필수입니다(dto.getIhidnum[%d] 번째 오류)",
                                    idx + 1));
                        }*/
                    }
                }

                //FIXME : KT 데이타 적합성 체크
                case KT_BC, KT_GIBIS -> {
                    isCiCreate = true;
                }

                case KAKAO_NEW -> {
                    if (Checks.isEmpty(dto.getCi())) {
                        isCiCreate = true;
                        if (Checks.isEmpty(dto.getIhidnum())) {
                            isError = true;
                            errors.add(
                                String.format("주민등록번호는 필수입니다(dto.getIhidnum[%d] 번째 오류)",
                                    idx + 1));
                        }
                    }
                }

                default -> {}
            }

            // CI 생성
            if(isCiCreate && !isError) {
                // 주민번호(ihidnum)로 등록된 ci가 있는지 조회 - 있으면 재활용
                // 사용되 지않은(등록안된) 주민번호 이면 null 반환
                // 해당 주민번호의 ci가 없으면, ci가 null
                Map<String, String> rsltMap = mapper.selectCiByIhidnum(dto.getEncIhidnum());
                if(rsltMap != null && rsltMap.get("ci") != null){
                    mapper.insertCi(
                        EnsDTO.NiceCiRslt.builder()
                            .unitySndngDetailId(dto.getUnitySndngDetailId())
                            .ci1(rsltMap.get("ci"))
                            .build()
                    );
                    isSuccess = true;
                    continue;
                }

                String ihidnumManageId;
                // 등록되지 않은 주민번호 등록
                if(rsltMap == null){
                    Map<String, Object> map = new HashMap<>();
                    map.put("ihidnumManageId", StringUtils.EMPTY);
                    map.put("ihidnum", dto.getEncIhidnum());

                    mapper.insertIhidnumManage(map);
                    ihidnumManageId = map.get("ihidnumManageId").toString();
                }else {
                    ihidnumManageId = rsltMap.get("ihidnumManageId");
                }

                // 춘천인 경우 DICI를 통한 CI 획득
                if(ApiConstants.IS_CCN) {
                    boolean isScc = requestNiceDici(dto, ihidnumManageId);
                    if(isScc)   isSuccess = true;
                }else{
                    boolean isScc = requestNiceCiApi(dto, ihidnumManageId, url);
                    if(isScc)   isSuccess = true;
                }
            }
            idx++;
        }

        if (!errors.isEmpty()) {
            throw BizRuntimeException.create(errors.toString());
        }
        return isSuccess;
    }

    /**
     * Nice Ci 생성 - 천안
     * @param reqDto EnsDTO.sndngValidation
     * @param ihidnumManageId 주민번호관리자id
     * @param url ci API URL
     * @return CI 생성 성공 여부
     */
    private boolean requestNiceCiApi(EnsDTO.SndngValidation reqDto, String ihidnumManageId, String url) {
        boolean isSuccess = false;

        NiceCiDTO.IpinCiResDataBody dataBody = apiWebClient.exchange(
            url,
            HttpMethod.POST,
            NiceCiDTO.NiceCiRequest.builder()
                .signguCode(reqDto.getSignguCode())
                .ffnlgCode(reqDto.getFfnlgCode())
                .juminId(reqDto.getIhidnum())
                .build(),
            NiceCiDTO.IpinCiResDataBody.class,
            CmmEnsBizUtils.getHeadeMap());

        NiceCiDTO.IpinCiResEncData resEncData = dataBody.toEncData();

        EnsDTO.NiceCiRslt rsltDto = EnsDTO.NiceCiRslt.builder()
            .unitySndngDetailId(reqDto.getUnitySndngDetailId())
            .build();

        if(resEncData == null) {
            //rsltDto.setResultCd(dataBody.getResultCd());
            rsltDto.setMessage("NICE-CI API 획득 실패: api log 확인");
            mapper.insertCi(rsltDto);
            return isSuccess;
        }

        rsltDto.setCi1(resEncData.getCi1());
        rsltDto.setCi2(resEncData.getCi2());
        rsltDto.setResultCd(dataBody.getResultCd());
        rsltDto.setMessage(messageUtil.getMessage("err.api.nice.ci." + dataBody.getResultCd()));
        mapper.insertCi(rsltDto);

        // CI가 return 되지 않으면(에러발생시), CI 관리 테이블 데이타 생성 skip
        if(resEncData.getCi1() != null) {
            isSuccess = true;
            mapper.insertCiManage(
                Map.of("ihidnumManageId", ihidnumManageId, "ci", resEncData.getCi1())
            );
        }
        return isSuccess;
    }

    /**
     * Nice Ci 생성 - 춘천
     * @param reqDto EnsDTO.sndngValidation
     * @param ihidnumManageId 주민번호관리자id
     *
     * @return CI 생성 성공 여부
     */
    private boolean requestNiceDici(EnsDTO.SndngValidation reqDto, String ihidnumManageId) {
        boolean isSuccess = false;

        EnsDTO.NiceCiRslt rslt = getDiCi(reqDto.getIhidnum(), true);
        rslt.setUnitySndngDetailId(reqDto.getUnitySndngDetailId());
        mapper.insertCi(rslt);

        // CI가 return 되지 않으면(에러발생시), CI 관리 테이블 데이타 생성 skip
        if(rslt.getCi1() != null){
            isSuccess = true;
            mapper.insertCiManage(
                Map.of("ihidnumManageId", ihidnumManageId, "ci", rslt.getCi1())
            );
        }
        return isSuccess;
    }

    private EnsDTO.NiceCiRslt getDiCi(@NonNull String ihidnum, boolean isCi) {
        int iRtn = -1;
        String errMsg = "";
        String ci = "";

        try {
            // 모듈 객체 생성
            VNOInterop vnoInterop = new VNOInterop();

            /* ──── DI or CI 값을 추출하기 위한 부분 Start */
            if(isCi) {
                iRtn = vnoInterop.fnRequestConnInfo(diciSiteCode, diciSitePwd, ihidnum, "JID");
            } else {
                iRtn = vnoInterop.fnRequestDupInfo(diciSiteCode, diciSitePwd, ihidnum, "JID");
            }
            switch (iRtn) {
                case 1 -> {
                    // iRtn 값이 정상이므로, 귀사의 기획의도에 맞게 진행하시면 됩니다.
                    // 아래와 같이 getDupInfo 함수를 통해 DI 값(64 Byte)을 추출할 수 있습니다.
                    if (isCi)
                        ci = vnoInterop.getConnInfo();
                    else
                        ci = vnoInterop.getDupInfo();
                }
                case 3 -> errMsg = "[사용자 정보와 실명확인 서비스 구분값 매핑 오류]";
                case -9 -> errMsg = "[입력값 오류]\nfnRequestDupInfo 함수 처리시, 필요한 4개의 파라미터값의 정보를 정확하게 입력해 주시기 바랍니다.";
                case -21, -31, -34 -> errMsg = "[통신오류]\n서버 네트웍크 및 방확벽 관련하여 아래 IP와 Port(5개)를 오픈해 주셔야 이용 가능합니다.\nIP : 203.234.219.72 / Port : 81, 82, 83, 84, 85";
                default -> errMsg = "return 값 확인 후, NICE평가정보 전산 담당자에게 문의해 주세요.\n[return 값:" + iRtn + "]";
            }
            if (!errMsg.isEmpty()){
                return EnsDTO.NiceCiRslt.builder()
                    .resultCd(String.valueOf(iRtn))
                    .message(errMsg)
                    .build();
            }

            return EnsDTO.NiceCiRslt.builder()
                .ci1(ci)
                .resultCd(String.valueOf(iRtn))
                .message(errMsg)
                .build();

        } catch (Exception e) {
            return EnsDTO.NiceCiRslt.builder()
                .resultCd(String.valueOf(iRtn))
                .message(e.getMessage())
                .build();
        }
    }

    /**
     * <pre>
     * 연계발송마스터 상태 변경
     * accpet 파라메처 체크오류시 마스터 상태 실패로 변경
     * @param mstIdList List<String>
     * @param stsErrMsg
     * </pre>
     * @see IEnsBatchMapper#updateProcessSttusCntcSndngMst(Object)
     */
    private void updateAcceptCntcSndngMstFailStatus(final List<String> mstIdList, final String stsErrMsg) {

        for (String id : mstIdList) {
            final SndngMssageParam dto = SndngMssageParam.builder()
                .unitySndngMastrId(id)
                .errorMssage(stsErrMsg)
                .build();

            CmmEnsBizUtils.setMappingProcessStatus(dto, SndngProcessStatus.ACCEPT_FAIL.getCode());
            if (mapper.updateProcessSttusCntcSndngMst(dto) != 1) {
                throw BizRuntimeException.create("[accept]연계 발송 마스터 상태변경 실패");
            }
        }
    }

    /**
     * <pre>
     * get 발송구분코드(문서중개자)
     * @param dto SndngValidation
     * @return SndngSeCode 발송구분코드(문서중개자)
     * </pre>
     */
    private static ApiConstants.SndngSeCode getSndngSeCodFromTrySeq(final EnsDTO.SndngValidation dto) {
        String[] tryVal = {
            Checks.checkVal(dto.getTry1(), ""),
            Checks.checkVal(dto.getTry2(), ""),
            Checks.checkVal(dto.getTry3(), "")
        };
        String seCode = tryVal[dto.getTrySeq() - 1];

        return ApiConstants.SndngSeCode.getSndngSeCode(seCode);
    }
    //-----------------------------------------------------------------------------------------------------------------
}
