package cokr.xit.ens.modules.kkomydoc.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.MapDeserailizer;
import cokr.xit.ens.modules.common.biztmplt.ResultProcTemplate;
import cokr.xit.ens.modules.common.code.StatCd;
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.domain.SendMast;
import cokr.xit.ens.modules.common.domain.repository.SendMastRepository;
import cokr.xit.ens.modules.common.domain.support.FieldError;
import cokr.xit.ens.modules.common.event.SendMastStatUpdateEvent;
import cokr.xit.ens.modules.kkomydoc.code.KkoMydocStatusCd;
import cokr.xit.ens.modules.kkomydoc.domain.SendDetailKkoMydoc;
import cokr.xit.ens.modules.kkomydoc.domain.SendDetailKkoMydocStatHist;
import cokr.xit.ens.modules.kkomydoc.domain.repository.SendDetailKkoMydocRepository;
import cokr.xit.ens.modules.kkomydoc.domain.repository.SendDetailKkoMydocStatHistRepository;
import cokr.xit.ens.modules.kkomydoc.model.KkoMydocApiRespVO;
import com.google.common.collect.Lists;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

@Slf4j
@Component
@RequiredArgsConstructor
public class KkoMydocRsltFetcher extends ResultProcTemplate {

    private final ApplicationEventPublisher applicationEventPublisher;

    private final SendMastRepository sendMastRepository;
    private final SendDetailKkoMydocRepository sendDetailKkoMydocRepository;
    private final SendDetailKkoMydocStatHistRepository sendDetailKkoMydocStatHistRepository;
    private final OrgMngService orgMngService;
    private final KkoMydocApiSpec kkoMydocApi;
    @Value("${contract.kakao.pay.mydoc.api.bulksend-batch-unit}")
    private int SEND_BATCH_UNIT;

    @Override
    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public EnsResponseVO execute(Long sendMastId) {


        SendMast sendMast = null;
        List<SendDetailKkoMydoc> sendDetails = null;
        EnsResponseVO respVO = null;
        Map<String, Long> resultInfo = new HashMap<>();
        resultInfo.put("sendMastId", sendMastId);
        try {
            if (CmmnUtil.isEmpty(sendMastId))
                throw new EnsException(EnsErrCd.RSLT410, "발송마스터ID(은)는 필수조건 입니다.");

            sendMast = sendMastRepository.findById(sendMastId).orElseThrow(() -> new EnsException(EnsErrCd.RSLT404, String.format("일치하는 발송마스터 자료가 없습니다. [ sendMastId %s ]", sendMastId)));
            sendDetails = sendDetailKkoMydocRepository.findAllBySendMastAndDocumentBinderUuidIsNotNull(sendMast);
            OrgMng orgMng = orgMngService.find(sendMast.getOrgCd()).getResultInfo();

            Lists.partition(sendDetails, SEND_BATCH_UNIT).stream()
                    .forEach(list -> {

                        String sendRespBody = null;
                        String jsonStr = null;
                        try {

                            EnsResponseVO message = this.makeMessage(list);
                            if (!(EnsErrCd.OK.equals(message.getErrCode()) || null == message.getErrCode())) 
                                throw new EnsException(message.getErrCode(), message.getErrMsg());
                            jsonStr = String.valueOf(message.getResultInfo());



                            ResponseEntity<String> resp = kkoMydocApi.bulkStatus(orgMng.getKkoMdAccessToken(), orgMng.getKkoMdContractUuid(), jsonStr);
                            if (log.isDebugEnabled()) {
                                StringBuffer sb = new StringBuffer();
                                sb.append("\n==============================================================================")
                                        .append("\n[ Kakao Mydoc - StatFetch ]")
                                        .append("\n### Request Info...")
                                        .append("\n" + jsonStr)
                                        .append("\n### Response Info...")
                                        .append("\n" + resp.getBody())
                                        .append("\n==============================================================================");
                                log.debug(sb.toString());
                            }
                           
                            sendRespBody = resp.getBody();
                            if (resp.getStatusCode() != HttpStatus.OK)  
                                throw new EnsException(EnsErrCd.RSLT620, String.format("문서상태조회 중.. %s %s", resp.getStatusCode().toString(), resp.getBody()));
                            EnsResponseVO mResponse = this.respMsgToMap(resp.getBody());
                            if (!EnsErrCd.OK.equals(mResponse.getErrCode()))
                                throw new EnsException(mResponse.getErrCode(), mResponse.getErrMsg());
                            List<Map<String, Object>> documents = (List<Map<String, Object>>) ((Map<String, Object>) mResponse.getResultInfo()).get("documents");



                            Map<String, KkoMydocApiRespVO> mApiRespVOByDocumentBinderUuid = documents.stream()
                                    .map(row -> this.toApiRespVOMap(row))
                                    .collect(Collectors.toMap(m -> String.valueOf(m.get("key")), m -> (KkoMydocApiRespVO) m.get("value"), (k1, k2) -> k1));
                           
                            list.stream().forEach(row -> this.modifyStatInfoByDocumentBinderUuid(row, mApiRespVOByDocumentBinderUuid));

                        } catch (EnsException e) {
                           
                            list.stream()
                                    .forEach(row -> row.setError(FieldError.initBuilder()
                                            .errorCode(e.getErrCd().getCode())
                                            .errorMessage(e.getMessage())
                                            .build())
                                    );
                        } catch (Exception e) {
                           
                            list.stream()
                                    .forEach(row -> row.setError(FieldError.initBuilder()
                                            .errorCode(EnsErrCd.RSLT500.getCode())
                                            .errorMessage(e.getMessage())
                                            .build())
                                    );
                        } finally {
                            if (!CmmnUtil.isEmpty(jsonStr))
                                sendDetailKkoMydocStatHistRepository.saveAll(this.toSendDetailStatHist(list, sendRespBody));
                        }

                    });

           
            respVO = EnsResponseVO.okBuilder().resultInfo(resultInfo).build();
        } 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 {
            if (!CmmnUtil.isEmpty(sendMast))
                if (EnsErrCd.OK.equals(respVO.getErrCode())) {
                    sendMast.setError(FieldError.initBuilder().build());

                   
                    Integer readCnt = sendDetails.stream()
                            .map(row -> KkoMydocStatusCd.READ.equals(row.getKkoDocStat()) ? 1 : 0)
                            .reduce(Integer::sum)
                            .orElseGet(() -> 0);
                    sendMast.setReadCnt(readCnt);
                   
                    if (StatCd.sendok.equals(sendMast.getStatCd())) { 
                        if (readCnt > 0)  
                            sendMast.setStatCd(StatCd.open);
                        else
                            sendMast.setStatCd(StatCd.sendcmplt);
                    } else if (StatCd.sendcmplt.equals(sendMast.getStatCd())) { 
                        if (readCnt > 0)  
                            sendMast.setStatCd(StatCd.open);
                    }
                } else {
                    sendMast.setError(FieldError.initBuilder()
                            .errorCode(respVO.getErrCode().getCode())
                            .errorMessage(respVO.getErrMsg())
                            .build());
                }


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


        return respVO;
    }


    /**
     * 문서상태조회API body Message로 변환하여 반환 한다.
     *
     * @param list
     * @return
     */
    private EnsResponseVO makeMessage(List<SendDetailKkoMydoc> list) {
        String result = null;
        Gson gson = new GsonBuilder().disableHtmlEscaping().create();
        EnsResponseVO resp = null;
        try {
           
            List<String> documentBinderUuids = list.stream()
                    .map(row -> row.getDocumentBinderUuid())
                    .collect(Collectors.toList());
            Map<String, Object> mJson = new HashMap<>();
            mJson.put("document_binder_uuids", documentBinderUuids);

           
            result = gson.toJson(mJson);

            resp = EnsResponseVO.okBuilder()
                    .resultInfo(result)
                    .build();
        } catch (EnsException e) {
            resp = EnsResponseVO.errBuilder()
                    .errCode(e.getErrCd())
                    .errMsg(e.getMessage())
                    .build();
        } catch (Exception e) {
            resp = EnsResponseVO.errBuilder()
                    .errCode(EnsErrCd.RSLT502)
                    .errMsg(String.format("요청 메시지 생성 중 오류 발생. %s", e.getMessage()))
                    .build();
        }

        return resp;
    }


    /**
     * 문서상태조회 응답결과를 API응답VO 객체에 매핑하여 반환
     *
     * @param row
     * @return
     */
    private Map<String, Object> toApiRespVOMap(Map<String, Object> row) {
        KkoMydocApiRespVO kkoMydocApiRespVO = KkoMydocApiRespVO.builder()
                .error_code(row.containsKey("error_code") ? String.valueOf(row.get("error_code")) : null)
                .error_message(row.containsKey("error_message") ? String.valueOf(row.get("error_message")) : null)
                .data(row.containsKey("status_data") ? row.get("status_data") : new HashMap<>())
                .build();

        Map<String, Object> m = new HashMap<>();
        m.put("key", row.get("document_binder_uuid"));
        m.put("value", kkoMydocApiRespVO);
        return m;
    }


    /**
     * 문서상태조회결과 갱신(update)
     * -. 성공/실패 여부에 따라 상태정보 및 Error 필드 갱신
     *
     * @param row
     * @param mMydocApiRespVOByDocumentBinderUuid
     */
    private void modifyStatInfoByDocumentBinderUuid(SendDetailKkoMydoc row, Map<String, KkoMydocApiRespVO> mMydocApiRespVOByDocumentBinderUuid) {
        KkoMydocApiRespVO apiRespVO = mMydocApiRespVOByDocumentBinderUuid.get(row.getDocumentBinderUuid());
        if (CmmnUtil.isEmpty(apiRespVO.getError_code())) {
            Map<String, Object> statusData = (Map<String, Object>) apiRespVO.getData();

            String docBoxStatus = statusData.containsKey("doc_box_status") ? (String) statusData.get("doc_box_status") : null;
            Long docBoxSentAt = statusData.containsKey("doc_box_sent_at") ? (Long) statusData.get("doc_box_sent_at") : null;
            Long docBoxReceivedAt = statusData.containsKey("doc_box_received_at") ? (Long) statusData.get("doc_box_received_at") : null;
            Long authenticatedAt = statusData.containsKey("authenticated_at") ? (Long) statusData.get("authenticated_at") : null;
            Long tokenUsedAt = statusData.containsKey("token_used_at") ? (Long) statusData.get("token_used_at") : null;
            Long docBoxReadAt = statusData.containsKey("doc_box_read_at") ? (Long) statusData.get("doc_box_read_at") : null;
            Long userNotifiedAt = statusData.containsKey("user_notified_at") ? (Long) statusData.get("user_notified_at") : null;

            row.setKkoDocStat(CmmnUtil.isEmpty(docBoxStatus) ? row.getKkoDocStat() : KkoMydocStatusCd.valueOfEnum(docBoxStatus)); 
            row.setKkoDocSentDt(CmmnUtil.isEmpty(row.getKkoDocSentDt()) ? DateUtil.absTimeSecToDate(docBoxSentAt, "yyyyMMddHHmmss") : row.getKkoDocSentDt());
            row.setKkoDocReceivedDt(CmmnUtil.isEmpty(row.getKkoDocReceivedDt()) ? DateUtil.absTimeSecToDate(docBoxReceivedAt, "yyyyMMddHHmmss") : row.getKkoDocReceivedDt());
            row.setKkoDocAuthFrstDt(CmmnUtil.isEmpty(row.getKkoDocAuthFrstDt()) ? DateUtil.absTimeSecToDate(authenticatedAt, "yyyyMMddHHmmss") : row.getKkoDocAuthFrstDt());
            row.setKkoDocTokenVrfyFrstDt(CmmnUtil.isEmpty(row.getKkoDocTokenVrfyFrstDt()) ? DateUtil.absTimeSecToDate(tokenUsedAt, "yyyyMMddHHmmss") : row.getKkoDocTokenVrfyFrstDt());
            row.setKkoDocReadFrstDt(CmmnUtil.isEmpty(row.getKkoDocReadFrstDt()) ? DateUtil.absTimeSecToDate(docBoxReadAt, "yyyyMMddHHmmss") : row.getKkoDocReadFrstDt());
            row.setKkoDocUserNotiedDt(CmmnUtil.isEmpty(row.getKkoDocUserNotiedDt()) ? DateUtil.absTimeSecToDate(userNotifiedAt, "yyyyMMddHHmmss") : row.getKkoDocUserNotiedDt());

            row.setError(FieldError.initBuilder().build());
        } else {
            if ("NOT_FOUND".equals(apiRespVO.getError_code()) && "documentBinder를 찾을 수 없습니다.".equals(apiRespVO.getError_message()))
                row.setKkoDocStat(KkoMydocStatusCd.INTERNAL_SENT_ERR);

            row.setError(FieldError.initBuilder()
                    .errorCode(EnsErrCd.RSLT630.getCode())
                    .errorMessage(String.format("%s %s", apiRespVO.getError_code(), apiRespVO.getError_message()))
                    .build());
        }
    }

    /**
     * 문서상태조회 응답을 전송요청이력 Entity 데이터로 변환하여 반환 한다.
     *
     * @param list
     * @param respMsg
     * @return
     */
    private List<SendDetailKkoMydocStatHist> toSendDetailStatHist(List<SendDetailKkoMydoc> list, String respMsg) {

//        Map<String, KkoMydocApiRespVO> mMydocApiRespVOByExtDocUuid =
                
        EnsResponseVO respVO = this.respMsgToMap(respMsg);

                
        if (EnsErrCd.OK.equals(respVO.getErrCode())) {

            List<Map<String, Object>> documents = (List<Map<String, Object>>) ((Map<String, Object>) respVO.getResultInfo()).get("documents");
            Map<String, KkoMydocApiRespVO> mRespVOByExtDocUuid = documents.stream()
                    .map(row -> this.toApiRespVOMap(row))
                    .collect(Collectors.toMap(m -> String.valueOf(m.get("key")), m -> (KkoMydocApiRespVO) m.get("value"), (k1, k2) -> k1));

            Gson gson = new GsonBuilder().disableHtmlEscaping().create();

            return list.stream()
                    .map(row -> {
                        KkoMydocApiRespVO apiRespVO = mRespVOByExtDocUuid.get(row.getDocumentBinderUuid());
                        Map<String, Object> statusData = (Map<String, Object>) apiRespVO.getData();
                        return SendDetailKkoMydocStatHist.builder()
                                .sendDetailId(row.getSendDetailId())
                                .documentBinderUuid(row.getDocumentBinderUuid())
                                .externalDocumentUuid(row.getPropExternalDocumentUuid())
//                                .respRawMsg(gson.toJson(statusData))
                                .docBoxStatus(statusData.containsKey("doc_box_status") ? (String) statusData.get("doc_box_status") : null)
                                .docBoxSentAt(statusData.containsKey("doc_box_sent_at") ? (Long) statusData.get("doc_box_sent_at") : null)
                                .docBoxReceivedAt(statusData.containsKey("doc_box_received_at") ? (Long) statusData.get("doc_box_received_at") : null)
                                .authenticatedAt(statusData.containsKey("authenticated_at") ? (Long) statusData.get("authenticated_at") : null)
                                .tokenUsedAt(statusData.containsKey("token_used_at") ? (Long) statusData.get("token_used_at") : null)
                                .docBoxReadAt(statusData.containsKey("doc_box_read_at") ? (Long) statusData.get("doc_box_read_at") : null)
                                .userNotifiedAt(statusData.containsKey("user_notified_at") ? (Long) statusData.get("user_notified_at") : null)
                                .docDistributionReceivedAt(statusData.containsKey("doc_distribution_received_at") ? (Long) statusData.get("doc_distribution_received_at") : null)
                                .payload(statusData.containsKey("payload") ? (String) statusData.get("payload") : null)
                                .error(FieldError.initBuilder()
                                        .errorCode(apiRespVO.getError_code())
                                        .errorMessage(apiRespVO.getError_message())
                                        .build())
                                .build();
                    })
                    .collect(Collectors.toList());

        } else {
            return list.stream()
                    .map(row -> {
                        return SendDetailKkoMydocStatHist.builder()
                                .sendDetailId(row.getSendDetailId())
                                .documentBinderUuid(row.getDocumentBinderUuid())
                                .externalDocumentUuid(row.getPropExternalDocumentUuid())
                                .error(FieldError.initBuilder()
                                        .errorCode(respVO.getErrCode().getCode())
                                        .errorMessage(respVO.getErrMsg())
                                        .build())
                                .build();
                    })
                    .collect(Collectors.toList());
        }
    }


    private EnsResponseVO respMsgToMap(String respMsg) {
        Gson gson = new GsonBuilder().registerTypeAdapter(Map.class, new MapDeserailizer()).serializeNulls().create();
        EnsErrCd errCode = EnsErrCd.OK;
        String errMsg = EnsErrCd.OK.getCodeNm();
        Map<String, Object> mResp = null;
        try {
            mResp = gson.fromJson(respMsg, Map.class);
            if (CmmnUtil.isEmpty(mResp.get("documents")))
                throw new EnsException(EnsErrCd.RSLT620, String.format("문서상태조회API 오류. documents 키값이 없습니다. %s", respMsg));
        } catch (EnsException e) {
            errCode = e.getErrCd();
            errMsg = e.getMessage();
        } catch (Exception e) {
            errCode = EnsErrCd.RSLT511;
            errMsg = String.format("문서상태조회API 응답 데이터 JSON 객체 변환 실패. %s", respMsg);
        }
        return EnsErrCd.OK.equals(errCode) ?
                EnsResponseVO.okBuilder().resultInfo(mResp).build()
                : EnsResponseVO.errBuilder()
                .errCode(errCode)
                .errMsg(errMsg)
                .build();
    }

}
