package cokr.xit.ens.modules.common.ctgy.intgrnnoti.service.support;

import cokr.xit.ens.core.aop.EnsResponseVO;
import cokr.xit.ens.core.exception.EnsException;
import cokr.xit.ens.core.exception.code.EnsErrCd;
import cokr.xit.ens.core.utils.CmmnUtil;
import cokr.xit.ens.core.utils.DateUtil;
import cokr.xit.ens.core.utils.IdGenerator;
import cokr.xit.ens.core.utils.MapDeserailizer;
import cokr.xit.ens.core.utils.crypto.AES256;
import cokr.xit.ens.core.utils.crypto.Crypto;
import cokr.xit.ens.modules.common.biztmplt.EnsPhaseProcSupport;
import cokr.xit.ens.modules.common.code.IntgrnDtlStatCd;
import cokr.xit.ens.modules.common.code.PostSeCd;
import cokr.xit.ens.modules.common.code.StatCd;
import cokr.xit.ens.modules.common.code.VenderCd;
import cokr.xit.ens.modules.common.ctgy.intgrnbill.support.entity.Bill;
import cokr.xit.ens.modules.common.ctgy.intgrnbill.support.entity.repository.BillRepository;
import cokr.xit.ens.modules.common.ctgy.intgrnnoti.code.TryStatCd;
import cokr.xit.ens.modules.common.ctgy.intgrnnoti.domain.IntgrnSendDetail;
import cokr.xit.ens.modules.common.ctgy.intgrnnoti.domain.IntgrnSendMast;
import cokr.xit.ens.modules.common.ctgy.intgrnnoti.domain.repository.IntgrnSendDetailRepository;
import cokr.xit.ens.modules.common.ctgy.intgrnnoti.domain.repository.IntgrnSendMastRepository;
import cokr.xit.ens.modules.common.ctgy.intgrnnoti.model.IntgrnNotiAcceptReqDTO;
import cokr.xit.ens.modules.common.ctgy.intgrnnoti.model.TmpltMngIntgrnDTO;
import cokr.xit.ens.modules.common.ctgy.sys.mng.domain.OrgMng;
import cokr.xit.ens.modules.common.ctgy.sys.mng.service.OrgMngService;
import cokr.xit.ens.modules.common.ctgy.sys.mng.service.TmpltMngService;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

import javax.validation.ConstraintViolation;
import javax.validation.Validation;
import javax.validation.Validator;
import java.util.*;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.stream.Collectors;

@Slf4j
@Component
@RequiredArgsConstructor
public class IntgrnNotiAcceptor implements EnsPhaseProcSupport<EnsResponseVO, IntgrnNotiAcceptReqDTO> {
    private final IntgrnSendMastRepository intgrnSendMastRepository;
    private final IntgrnSendDetailRepository intgrnSendDetailRepository;
    private final BillRepository billRepository;
    private final OrgMngService orgMngService;
    private final TmpltMngService tmpltMngService;

    private Gson gson = new GsonBuilder().registerTypeAdapter(Map.class, new MapDeserailizer()).disableHtmlEscaping().create();

    @Override
    public EnsResponseVO statReady(List<IntgrnNotiAcceptReqDTO> reqDTOs) {
        log.info("no process");
        return null;
    }

    @Override
    public EnsResponseVO statBegin(IntgrnNotiAcceptReqDTO reqDTO) {
        final Validator validator = Validation.buildDefaultValidatorFactory().getValidator();
        Set<ConstraintViolation<IntgrnNotiAcceptReqDTO>> list = validator.validate(reqDTO);
        if (list.size() > 0) {
            return EnsResponseVO.errRsltBuilder()
                    .errCode(EnsErrCd.ACPT402)
                    .errMsg("유효하지 않은 요청값 입니다.")
                    .resultInfo(list.stream()
                            .map(row -> String.format("%s [ %s ]", row.getMessageTemplate(), row.getPropertyPath()))
                            .collect(Collectors.toList()))
                    .build();
        }
        List<String> expiredDateValidateList = expiredDateVaildate(reqDTO);
        if (expiredDateValidateList.size() > 0) {
            return EnsResponseVO.errRsltBuilder()
                    .errCode(EnsErrCd.ACPT402)
                    .errMsg("유효하지 않은 유효기한 입니다. 자료의 유효기한은 마감일시 이내이어야 합니다.")
                    .resultInfo(expiredDateValidateList)
                    .build();
        }
        EnsResponseVO orgMng = orgMngService.find(reqDTO.getOrg_cd());
        if (!EnsErrCd.OK.equals(orgMng.getErrCode()))
            return EnsResponseVO.errRsltBuilder()
                    .errCode(EnsErrCd.ACPT404)
                    .errMsg("일치하는 기관정보가 없습니다.")
                    .resultInfo(orgMng.getErrMsg())
                    .build();
        EnsResponseVO tmpltMng = tmpltMngService.find(reqDTO.getOrg_cd(), reqDTO.getTmplt_cd(), "intgrn");
        if (!EnsErrCd.OK.equals(tmpltMng.getErrCode()))
            return EnsResponseVO.errRsltBuilder()
                    .errCode(EnsErrCd.ACPT404)
                    .errMsg("일치하는 템플릿정보가 없습니다.")
                    .resultInfo(tmpltMng.getErrMsg())
                    .build();


        return EnsResponseVO.okBuilder().build();
    }

    private List<String> expiredDateVaildate(IntgrnNotiAcceptReqDTO reqDTO) {
        List<String> result = new ArrayList<>();

        AtomicInteger i = new AtomicInteger();
        reqDTO.getDocuments().forEach(document -> {
            if (!CmmnUtil.isEmpty(document.getAcpt_data().getKko_md())) {
                if (!CmmnUtil.isEmpty(document.getAcpt_data().getKko_md().getRead_expired_at())) {
                    String expDate = DateUtil.absTimeSecToDate(document.getAcpt_data().getKko_md().getRead_expired_at(), "yyyyMMddHHmmss");
                    int sec = DateUtil.secByFromBetweenTo(DateUtil.toLocalDateTime(expDate), DateUtil.toLocalDateTime(reqDTO.getClose_dt()));
                    if (sec < 0)
                        result.add(String.format("마감일시보다 \"처리마감시간(절대시간)\"이 느립니다. [ document[%d].acpt_data.kko_md.read_expired_at ]", i.get()));
                }
                if (!CmmnUtil.isEmpty(document.getAcpt_data().getKko_md().getRead_expired_sec())) {
                    String expDate = DateUtil.addRelTimeSecToDate(DateUtil.toLocalDateTime(reqDTO.getSend_dt()), document.getAcpt_data().getKko_md().getRead_expired_sec(), "yyyyMMddHHmmss");
                    int sec = DateUtil.secByFromBetweenTo(DateUtil.toLocalDateTime(expDate), DateUtil.toLocalDateTime(reqDTO.getClose_dt()));
                    if (sec < 0)
                        result.add(String.format("마감일시보다 \"처리마감시간(상대시간)\"이 느립니다. [ document[%d].acpt_data.kko_md.read_expired_sec ]", i.get()));
                }
            }
            if (!CmmnUtil.isEmpty(document.getAcpt_data().getNv_st())) {
                if (!CmmnUtil.isEmpty(document.getAcpt_data().getNv_st().getValid_duration_at())) {
                    String expDate = DateUtil.absTimeSecToDate(document.getAcpt_data().getNv_st().getValid_duration_at(), "yyyyMMddHHmmss");
                    int sec = DateUtil.secByFromBetweenTo(DateUtil.toLocalDateTime(expDate), DateUtil.toLocalDateTime(reqDTO.getClose_dt()));
                    if (sec < 0)
                        result.add(String.format("마감일시보다 \"고지서 열람 유효시간(초)-절대시간\"이 느립니다. [ document[%d].acpt_data.nv_st.valid_duration_at ]", i.get()));
                }
                if (!CmmnUtil.isEmpty(document.getAcpt_data().getNv_st().getValid_duration())) {
                    String expDate = DateUtil.addRelTimeSecToDate(DateUtil.toLocalDateTime(reqDTO.getSend_dt()), document.getAcpt_data().getNv_st().getValid_duration().intValue(), "yyyyMMddHHmmss");
                    int sec = DateUtil.secByFromBetweenTo(DateUtil.toLocalDateTime(expDate), DateUtil.toLocalDateTime(reqDTO.getClose_dt()));
                    if (sec < 0)
                        result.add(String.format("마감일시보다 \"고지서 열람 유효시간(초)-상대시간\"이 느립니다. [ document[%d].acpt_data.nv_st.valid_duration ]", i.get()));
                }
            }
            if (!CmmnUtil.isEmpty(document.getAcpt_data().getKt_st())) {
                if (!CmmnUtil.isEmpty(document.getAcpt_data().getKt_st().getExTime())) {
                    String expDate = document.getAcpt_data().getKt_st().getExTime();
                    int sec = DateUtil.secByFromBetweenTo(DateUtil.toLocalDateTime(expDate), DateUtil.toLocalDateTime(reqDTO.getClose_dt()));
                    if (sec < 0)
                        result.add(String.format("마감일시보다 \"처리마감시간\"이 느립니다. [ document[%d].acpt_data.kt_st.extTime ]", i.get()));
                }

            }
            i.getAndIncrement();
        });

        return result;
    }

    @Override
    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public EnsResponseVO<List<IntgrnSendDetail>> execute(IntgrnNotiAcceptReqDTO reqDTO) throws EnsException {
        AES256 aes256 = new AES256(Crypto.AES256.getKey());
        EnsResponseVO responseVO = null;
        try {
            final TmpltMngIntgrnDTO tmpltMngIntgrnDTO = (TmpltMngIntgrnDTO) tmpltMngService.find(reqDTO.getOrg_cd(), reqDTO.getTmplt_cd(), "intgrn").getResultInfo();


            int tryCnt = 0;
            if (!CmmnUtil.isEmpty(tmpltMngIntgrnDTO.getTry1())) tryCnt++;
            if (!CmmnUtil.isEmpty(tmpltMngIntgrnDTO.getTry2())) tryCnt++;
            if (!CmmnUtil.isEmpty(tmpltMngIntgrnDTO.getTry3())) tryCnt++;
            IntgrnSendMast intgrnSendMast = IntgrnSendMast.builder()
                    .vender(VenderCd.valueOfEnum(reqDTO.getVender()))
                    .orgCd(reqDTO.getOrg_cd())
                    .tmpltCd(reqDTO.getTmplt_cd())
                    .postBundleTitle(reqDTO.getPost_bundle_title())
                    .statCd(StatCd.accept)
                    .sendDt(DateUtil.toLocalDateTime(reqDTO.getSend_dt()))
                    .sendCnt(reqDTO.getDocuments().size())
                    .closeDt(DateUtil.toLocalDateTime(reqDTO.getClose_dt()))
                    .try1(tmpltMngIntgrnDTO.getTry1())
                    .try2(tmpltMngIntgrnDTO.getTry2())
                    .try3(tmpltMngIntgrnDTO.getTry3())
                    .tryCnt(tryCnt)
                    .trySeq(0)
                    .tryStatCd(TryStatCd.stby)
                    .build();
            intgrnSendMastRepository.save(intgrnSendMast);


            Gson gson = new GsonBuilder().disableHtmlEscaping().create();
            List<IntgrnSendDetail> intgrnSendDetails = new ArrayList<>();
            List<Bill> bills = new ArrayList<>();
            final String prefixBillUid = PostSeCd.intgrnNoti.getCode() + "-" + IdGenerator.getCurrentTimeSec();
            AtomicInteger i = new AtomicInteger();
            reqDTO.getDocuments().forEach(document -> {
                i.getAndIncrement();
                try {

                    Bill bill = null;
                    if (!CmmnUtil.isEmpty(document.getXit_property())
                            && !CmmnUtil.isEmpty(document.getXit_property().getBill_acpt_data())) {
//                        if (document.getXit_property().getBill_acpt_data().getUse_bill_uid()) {
                        if (!CmmnUtil.isEmpty(document.getXit_property().getBill_acpt_data().getBillUid())) {
                            bill = billRepository.findByBillUid(document.getXit_property().getBill_acpt_data().getBillUid())
                                    .orElseThrow(() -> new RuntimeException("등록된 청구서가 없습니다."));
                        } else {
                            bill = Bill.builder()
//                                    .billId(document.getXit_property().getBill_acpt_data().getBill_id())
                                    .billId(null)
                                    .billUid(IdGenerator.getShortUUID(prefixBillUid))
                                    .billerUserKey(document.getXit_property().getBill_acpt_data().getBillerUserKey())
                                    .billSeCd(document.getXit_property().getBill_acpt_data().getBillSe())
                                    .orgMng(OrgMng.builder().orgCd(reqDTO.getOrg_cd()).build())
//                                    .docBillKko(CmmnUtil.isEmpty(document.getXit_property().getBill_acpt_data().getBill_kko()) ? null : gson.toJson(document.getXit_property().getBill_acpt_data().getBill_kko()))
//                                    .docBillNv(CmmnUtil.isEmpty(document.getXit_property().getBill_acpt_data().getBill_nv()) ? null : gson.toJson(document.getXit_property().getBill_acpt_data().getBill_nv()))
                                    .build();
                        }
                        bills.add(bill);
                    }

                    IntgrnSendDetail sendDetail = IntgrnSendDetail.builder()
                            .intgrnSendMast(intgrnSendMast)
                            .linkedUuid(document.getLinked_uuid())
                            .ci(document.getReceiver().getCi())
//                            .jid(document.getReceiver().getJid())
                            .jid(aes256.encrypt(document.getReceiver().getJid()))
                            .mblphonNo(document.getReceiver().getMblphon_no())
                            .jTmpltMsgData(CmmnUtil.isEmpty(document.getXit_property().getTmplt_msg_data()) ? null : gson.toJson(document.getXit_property().getTmplt_msg_data()))
                            .jMblPageData(CmmnUtil.isEmpty(document.getXit_property().getMbl_page_data()) ? null : gson.toJson(document.getXit_property().getMbl_page_data()))
                            .jAcptDocKkoAt(CmmnUtil.isEmpty(document.getAcpt_data().getKko_at()) ? null : gson.toJson(document.getAcpt_data().getKko_at()))
                            .jAcptDocKkoMd(CmmnUtil.isEmpty(document.getAcpt_data().getKko_md()) ? null : gson.toJson(document.getAcpt_data().getKko_md()))
                            .jAcptDocNvSt(CmmnUtil.isEmpty(document.getAcpt_data().getNv_st()) ? null : gson.toJson(document.getAcpt_data().getNv_st()))
                            .jAcptDocKtSt(CmmnUtil.isEmpty(document.getAcpt_data().getKt_st()) ? null : gson.toJson(document.getAcpt_data().getKt_st()))
                            .jAcptDocKtGbs(CmmnUtil.isEmpty(document.getAcpt_data().getKt_gbs()) ? null : gson.toJson(document.getAcpt_data().getKt_gbs()))
                            .curPostSe(tmpltMngIntgrnDTO.getTry1())
                            .curStatCd(IntgrnDtlStatCd.REG)
                            .sendDetailIdKkoAt(null)
                            .sendDetailIdKkoMd(null)
                            .sendDetailIdNvSt(null)
                            .sendDetailIdKtSt(null)
                            .sendDetailIdKtGbs(null)
                            .mkBillUseYn(CmmnUtil.isEmpty(bill) ? "N" : "Y")
//                            .mkBillUid(bill == null ? null : bill.getBillUid())
                            .bill(bill)
                            .build();
                    intgrnSendDetails.add(sendDetail);

                } catch (Exception e) {
                    throw new EnsException(EnsErrCd.ACPT500, String.format("%d번째 데이터 접수처리 중 오류 발생. 실패사유: %s", i.get(), e.getMessage()));
                }
            });
            if (CmmnUtil.isEmpty(intgrnSendDetails))
                throw new EnsException(EnsErrCd.ACPT410, "발송상세 자료가 없습니다.");


            billRepository.saveAll(bills.stream()
                    .filter(row -> CmmnUtil.isEmpty(row.getBillId()))
                    .collect(Collectors.toList())
            );
            intgrnSendDetailRepository.saveAll(intgrnSendDetails);


//            MakerFactory makerFactory = new AcceptDataMakerFactory(intgrnSendMast, intgrnSendDetails, true);
//
//            applicationEventPublisher.publishEvent(makerFactory.get(reqDTO.getTry1()));
//
//            intgrnSendMast.setTrySeq(1);
//            intgrnSendMast.setTryStatCd(TryStatCd.sndRsrv);
//            intgrnSendMastRepository.save(intgrnSendMast);

            Map<String, Object> resultInfo = new HashMap<>();
            resultInfo.put("intSendMastId", intgrnSendMast.getIntSendMastId());
            responseVO = EnsResponseVO.okBuilder().resultInfo(resultInfo).build();
        } catch (EnsException e) {
            throw e;
        } catch (Exception e) {
            throw new EnsException(EnsErrCd.ACPT500, String.format("오류가 발생하여 접수에 실패 했습니다. 실패사유: %s", e.getMessage()));
        }

        return responseVO;
    }


}
