package cokr.xit.ens.modules.ktsigntalk.gibis.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.MapDeserailizer;
import cokr.xit.ens.modules.common.biztmplt.EnsPhaseProcSupport;
import cokr.xit.ens.modules.common.code.StatCd;
import cokr.xit.ens.modules.common.domain.QSendMast;
import cokr.xit.ens.modules.common.domain.SendMast;
import cokr.xit.ens.modules.common.domain.support.FieldError;
import cokr.xit.ens.modules.common.event.SendMastStatUpdateEvent;
import cokr.xit.ens.modules.ktsigntalk.gibis.code.KtGibisStatusCd;
import cokr.xit.ens.modules.ktsigntalk.gibis.domain.SendDetailKtGibis;
import cokr.xit.ens.modules.ktsigntalk.gibis.domain.SendDetailKtGibisStatHist;
import cokr.xit.ens.modules.ktsigntalk.gibis.domain.repository.SendDetailKtGibisRepository;
import cokr.xit.ens.modules.ktsigntalk.gibis.domain.repository.SendDetailKtGibisStatHistRepository;
import cokr.xit.ens.modules.ktsigntalk.gibis.model.KtGibisRsltStatFwdDTO;
import cokr.xit.ens.modules.ktsigntalk.gibis.model.config.DocStatResp;
import com.google.common.collect.Lists;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.querydsl.core.Tuple;
import com.querydsl.core.types.dsl.CaseBuilder;
import com.querydsl.jpa.impl.JPAQueryFactory;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

import java.time.LocalDateTime;
import java.util.*;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.stream.Collectors;

import static cokr.xit.ens.modules.ktsigntalk.gibis.domain.QSendDetailKtGibis.sendDetailKtGibis;

@Slf4j
@Component
@RequiredArgsConstructor
public class KtGibisRsltFetcher implements EnsPhaseProcSupport<EnsResponseVO, KtGibisRsltStatFwdDTO> {

    private final ApplicationEventPublisher applicationEventPublisher;
    private final JPAQueryFactory query;

    private final SendDetailKtGibisRepository sendDetailKtGibisRepository;
    private final SendDetailKtGibisStatHistRepository sendDetailKtGibisStatHistRepository;
    @Value("${contract.kakao.pay.mydoc.api.bulksend-batch-unit}")
    private int SEND_BATCH_UNIT;

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

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

    @Override
    public EnsResponseVO statBegin(KtGibisRsltStatFwdDTO arg) {
        log.info("no process");
        return null;
    }

    @Override
    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public EnsResponseVO execute(KtGibisRsltStatFwdDTO dto) {

        EnsResponseVO respVO = null;
        Set<Long> sendMastIds = new HashSet<>();
        Map<String, Set<Long>> resultInfo = new HashMap<>();
        resultInfo.put("sendMastIds", sendMastIds);
        try {
            AtomicInteger cntSuccess = new AtomicInteger();

            Lists.partition(dto.getReqs(), SEND_BATCH_UNIT).stream()
                    .forEach(partdocStatResps -> {

                        Map<String, Long> mSendDetailIdBySrcKey = new HashMap<>();
                        try {
                            List<String> srcKeys = partdocStatResps.stream().map(DocStatResp::getSrc_key).collect(Collectors.toList());
                            List<Tuple> tuples = query.select(sendDetailKtGibis.sendMast.sendMastId, sendDetailKtGibis.sendDetailId, sendDetailKtGibis.srcKey)
                                    .from(sendDetailKtGibis)
                                    .where(sendDetailKtGibis.srcKey.in(srcKeys))
                                    .fetch();
                            if (CmmnUtil.isEmpty(tuples))
                                throw new EnsException(EnsErrCd.RSLT404, "관리키(src_key)와 일치하는 자료가 없습니다.");
                           
//                            if (srcKeys.size() != tuples.size())
//                                throw new EnsException(EnsErrCd.RSLT405, String.format("%s(요청:%s 실제:%s)", EnsErrCd.RSLT405.getCodeNm(), srcKeys.size(), tuples.size()));


                            tuples.stream().forEach(tuple -> sendMastIds.add(tuple.get(sendDetailKtGibis.sendMast.sendMastId)));


                            mSendDetailIdBySrcKey = tuples.stream()
                                    .collect(Collectors.toMap(m -> m.get(sendDetailKtGibis.srcKey), m -> m.get(sendDetailKtGibis.sendDetailId), (k1, k2) -> k1));
                            partdocStatResps.stream()
                                    .forEach(docStatResp -> {
                                       
                                        partdocStatResps.stream().forEach(row -> this.modifyStatInfoBySrcKey(docStatResp));
                                    });
                            cntSuccess.addAndGet(partdocStatResps.size());

                        } catch (EnsException e) {
                           
                            query.update(sendDetailKtGibis)
                                    .set(sendDetailKtGibis.error.errorCode, e.getErrCd().getCode())
                                    .set(sendDetailKtGibis.error.errorMessage, e.getMessage())
                                    .where(sendDetailKtGibis.srcKey.in(partdocStatResps.stream().map(DocStatResp::getSrc_key).collect(Collectors.toList())))
                                    .execute();
                        } catch (Exception e) {
                           
                            query.update(sendDetailKtGibis)
                                    .set(sendDetailKtGibis.error.errorCode, EnsErrCd.RSLT999.getCode())
                                    .set(sendDetailKtGibis.error.errorMessage, e.getMessage())
                                    .where(sendDetailKtGibis.srcKey.in(partdocStatResps.stream().map(DocStatResp::getSrc_key).collect(Collectors.toList())))
                                    .execute();
                        } finally {
                            sendDetailKtGibisStatHistRepository.saveAll(this.toSendDetailStatHist(partdocStatResps, dto.getService_cd(), mSendDetailIdBySrcKey));
                        }


                    });


           
            if (cntSuccess.get() == dto.getReqs().size())
                respVO = EnsResponseVO.okBuilder().resultInfo(resultInfo).build();
            else if (cntSuccess.get() == 0)
                throw new EnsException(EnsErrCd.RSLT407, String.format("%s(%d건)", EnsErrCd.RSLT407.getCodeNm(), dto.getReqs().size()));
            else
                throw new EnsException(EnsErrCd.RSLT406, String.format("%s(성공:%d건 실패:%d건)", EnsErrCd.RSLT406.getCodeNm(), cntSuccess.get(), dto.getReqs().size() - cntSuccess.get()));
        } catch (EnsException e) {
           
            respVO = EnsResponseVO.errRsltBuilder().errCode(e.getErrCd()).errMsg(e.getMessage()).resultInfo(resultInfo).build();
        } catch (Exception e) {
           
            respVO = EnsResponseVO.errRsltBuilder().errCode(EnsErrCd.RSLT500).errMsg(e.getMessage()).resultInfo(resultInfo).build();
        } finally {
            sendMastIds.forEach(sendMastId -> {

                List<SendDetailKtGibis> sendDetails = sendDetailKtGibisRepository.findAllBySendMast(SendMast.builder().sendMastId(sendMastId).build());
                Integer readCnt = sendDetails.stream()
                        .map(row -> KtGibisStatusCd.E60.equals(row.getDocStat()) ? 1 : 0)
                        .reduce(Integer::sum)
                        .orElseGet(() -> 0);
               
                SendMast sendMast = sendDetails.get(0).getSendMast();
                StatCd statCd = sendMast.getStatCd();
                if (StatCd.sendok.equals(sendMast.getStatCd())) { 
                    if (readCnt > 0)  
                        statCd = StatCd.open;
                    else
                        statCd = StatCd.sendcmplt;
                } else if (StatCd.sendcmplt.equals(sendMast.getStatCd())) { 
                    if (readCnt > 0)  
                        statCd = StatCd.open;
                }
               
                query.update(QSendMast.sendMast)
                        .set(QSendMast.sendMast.readCnt, readCnt)
                        .set(QSendMast.sendMast.statCd, statCd)
                        .set(QSendMast.sendMast.lastUpdtDt, LocalDateTime.now())
                        .where(QSendMast.sendMast.sendMastId.eq(sendMastId))
                        .execute();


                applicationEventPublisher.publishEvent(SendMastStatUpdateEvent.builder().sendMastId(sendMastId).build());
            });


        }

        return respVO;

    }


    /**
     * 문서상태조회결과 갱신(update)
     * -. 성공/실패 여부에 따라 상태정보 및 Error 필드 갱신
     *
     * @param docStatResp
     */
    private void modifyStatInfoBySrcKey(DocStatResp docStatResp) {

        KtGibisStatusCd docStatusCd = KtGibisStatusCd.valueOfEnum("E" + docStatResp.getMms_rslt_dvcd());
        if(KtGibisStatusCd.E40.equals(docStatusCd)
                && "2".equals(String.valueOf(docStatResp.getMms_sndg_rslt_sqno()))
                && !CmmnUtil.isEmpty(docStatResp.getMms_rdg_tmst()))
            docStatusCd = KtGibisStatusCd.E60;
        String docBoxSentAt = docStatResp.getMms_sndg_tmst() == null ? "" : docStatResp.getMms_sndg_tmst();
        String docBoxReceivedAt = docStatResp.getMms_rcv_tmst() == null ? "" : docStatResp.getMms_rcv_tmst();
        String authenticatedAt = "";
        String tokenUsedAt = "";
        String docBoxReadAt = docStatResp.getMms_rdg_tmst() == null ? "" : docStatResp.getMms_rdg_tmst();


        query.update(sendDetailKtGibis)
                .set(sendDetailKtGibis.docStat, docStatusCd)
                .set(sendDetailKtGibis.docSentDt, docBoxSentAt)
                .set(sendDetailKtGibis.docReceivedDt, new CaseBuilder().when(sendDetailKtGibis.docReceivedDt.isNull()).then(docBoxReceivedAt).otherwise(sendDetailKtGibis.docReceivedDt))
                .set(sendDetailKtGibis.docAuthFrstDt, new CaseBuilder().when(sendDetailKtGibis.docAuthFrstDt.isNull()).then(authenticatedAt).otherwise(sendDetailKtGibis.docAuthFrstDt)) 
                .set(sendDetailKtGibis.docTokenVrfyFrstDt, new CaseBuilder().when(sendDetailKtGibis.docTokenVrfyFrstDt.isNull()).then(tokenUsedAt).otherwise(sendDetailKtGibis.docTokenVrfyFrstDt))
                .set(sendDetailKtGibis.docReadFrstDt, new CaseBuilder().when(sendDetailKtGibis.docReadFrstDt.isNull()).then(docBoxReadAt).otherwise(sendDetailKtGibis.docReadFrstDt))    
//                .set(sendDetailKtGibis.error.errorCode, "")
//                .set(sendDetailKtGibis.error.errorMessage, "")
                .set(sendDetailKtGibis.error, FieldError.initBuilder().build())
                .where(sendDetailKtGibis.srcKey.eq(docStatResp.getSrc_key()))
                .execute();

    }

    /**
     * 문서상태조회 응답을 전송요청이력 Entity 데이터로 변환하여 반환 한다.
     *
     * @param list
     * @return
     */
    private List<SendDetailKtGibisStatHist> toSendDetailStatHist(List<DocStatResp> list, String serviceCd, Map<String, Long> mSendDetailId) {
        return list.stream()
                .map(row -> {
                    return SendDetailKtGibisStatHist.builder()
                            .sendDetailId(mSendDetailId.getOrDefault(row.getSrc_key(), -1L))
                            .srcKey(row.getSrc_key())
                            .serviceCd(serviceCd)
                            .mmsSndgRsltSqno(row.getMms_sndg_rslt_sqno())
                            .prcsDt(row.getPrcs_dt())
                            .mmsBsnsDvcd(row.getMms_bsns_dvcd())
                            .mblBzowrDvcd(row.getMbl_bzowr_dvcd())
                            .rlMmsSndgTelno(row.getRl_mms_sndg_telno())
                            .mmsRsltDvcd(row.getMms_rslt_dvcd())
                            .mmsSndgTmst(row.getMms_sndg_tmst())
                            .mmsRcvTmst(row.getMms_rcv_tmst())
                            .mmsRdgTmst(row.getMms_rdg_tmst())
                            .prevApproveYn(row.getPrev_approve_yn())
                            .build();
                })
                .collect(Collectors.toList());
    }

}
