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.monitor.slack.event.MonitorEvent;
import cokr.xit.ens.core.utils.CmmnUtil;
import cokr.xit.ens.core.utils.MapDeserailizer;
import cokr.xit.ens.modules.common.biztmplt.SendProcTemplate;
import cokr.xit.ens.modules.common.code.PostSeCd;
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.common.monitor.MessageByPhase;
import cokr.xit.ens.modules.kkomydoc.code.KkoMydocStatusCd;
import cokr.xit.ens.modules.kkomydoc.domain.SendDetailKkoMydoc;
import cokr.xit.ens.modules.kkomydoc.domain.SendDetailKkoMydocReqHist;
import cokr.xit.ens.modules.kkomydoc.domain.repository.SendDetailKkoMydocRepository;
import cokr.xit.ens.modules.kkomydoc.domain.repository.SendDetailKkoMydocReqHistRepository;
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.*;
import java.util.stream.Collectors;

@Slf4j
@Component
@RequiredArgsConstructor
public class KkoMydocSender extends SendProcTemplate {

    private final ApplicationEventPublisher applicationEventPublisher;
    private final SendMastRepository sendMastRepository;
    private final SendDetailKkoMydocRepository sendDetailKkoMydocRepository;
    private final SendDetailKkoMydocReqHistRepository sendDetailKkoMydocReqHistRepository;
    private final OrgMngService orgMngService;
    private final KkoMydocApiSpec kkoMydocApi;
    @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
    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public EnsResponseVO execute(Long sendMastId) {

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

            sendMast = sendMastRepository.findById(sendMastId).orElseThrow(() -> new EnsException(EnsErrCd.SEND404, String.format("일치하는 발송마스터 자료가 없습니다. [ sendMastId %s ]", sendMastId)));
            sendDetails = sendDetailKkoMydocRepository.findAllFetchBySendMastId(sendMast.getSendMastId());
            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.bulkSend(orgMng.getKkoMdAccessToken(), orgMng.getKkoMdContractUuid(), jsonStr);
                            if (log.isDebugEnabled()) {
                                StringBuffer sb = new StringBuffer();
                                sb.append("\n==============================================================================")
                                        .append("\n[ Kakao Mydoc - Sending ]")
                                        .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.SEND620, 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> mApiRespVOByExtDocUuid = 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.modifyDocSendRsltByExtDocUuid(row, mApiRespVOByExtDocUuid));

                        } catch (EnsException e) {

                            list.stream()
                                    .forEach(row -> {
                                        row.setKkoDocStat(KkoMydocStatusCd.SENT_FAIL);
                                        row.setError(FieldError.initBuilder()
                                                .errorCode(e.getErrCd().getCode())
                                                .errorMessage(e.getMessage())
                                                .build());
                                    });
                        } catch (Exception e) {

                            list.stream()
                                    .forEach(row -> {
                                        row.setKkoDocStat(KkoMydocStatusCd.SENT_FAIL);
                                        row.setError(FieldError.initBuilder()
                                                .errorCode(EnsErrCd.SEND500.getCode())
                                                .errorMessage(e.getMessage())
                                                .build());
                                    });
                        } finally {
                            if (!CmmnUtil.isEmpty(jsonStr))
                                sendDetailKkoMydocReqHistRepository.saveAll(this.toSendDetailReqHist(list, jsonStr, sendRespBody));
                        }

                    });


            Optional<Integer> cntSuccess = sendDetails.stream()
                    .map(row -> CmmnUtil.isEmpty(row.getError()) || CmmnUtil.isEmpty(row.getError().getErrorCode()) ? 1 : 0)
                    .reduce(Integer::sum);
            if (cntSuccess.get() > 0)
                respVO = EnsResponseVO.okBuilder()
                        .resultInfo(sendDetails.stream()
                                .map(row -> this.toResultInfo(row))
                                .collect(Collectors.toList()))
                        .build();
            else
                throw new EnsException(EnsErrCd.SEND500, String.format("전체건수 전송 실패(총 %d 건)", sendDetails.size()));

        } catch (EnsException e) {

            respVO = EnsResponseVO.errBuilder().errCode(e.getErrCd()).errMsg(e.getMessage()).build();
        } catch (Exception e) {

            respVO = EnsResponseVO.errBuilder().errCode(EnsErrCd.SEND500).errMsg(e.getMessage()).build();
        } finally {
            if (!CmmnUtil.isEmpty(sendMast)) {
                if (EnsErrCd.OK.equals(respVO.getErrCode())) {
                    sendMast.setStatCd(StatCd.sendok);
                    sendMast.setError(FieldError.initBuilder().build());
                } else {
                    sendMast.setStatCd(StatCd.sendfail);
                    sendMast.setError(FieldError.initBuilder()
                            .errorCode(respVO.getErrCode().getCode())
                            .errorMessage(respVO.getErrMsg())
                            .build());

                    applicationEventPublisher.publishEvent(MonitorEvent.builder()
                            .message(MessageByPhase.builder()
                                    .oClass(getClass().getSimpleName() + "." + new Throwable().getStackTrace()[0].getMethodName())
                                    .postSeCd(PostSeCd.kkoMydoc)
                                    .statCd(StatCd.sendfail)
                                    .errCd(respVO.getErrCode())
                                    .message("-.SendMastId: " + sendMastId + "\n" + respVO.getErrMsg())
                                    .build())
                            .build());
                }
            }

//            if (respVO != null && EnsErrCd.OK.equals(respVO.getErrCode())) {
//                applicationEventPublisher.publishEvent(KkoMydocSentEvent.builder().sendMastId(sendMastId).build());
//            } else {
            applicationEventPublisher.publishEvent(SendMastStatUpdateEvent.builder().sendMastId(sendMastId).build());
//            }

        }


        return respVO;
    }


    /**
     * 전송요청API body Message로 변환하여 반환 한다.
     *
     * @param list
     * @return
     */
    private EnsResponseVO makeMessage(List<SendDetailKkoMydoc> list) {
        String result = null;
        EnsResponseVO resp = null;
        try {


            List<Map<String, Object>> documents = new ArrayList<>();
            for (SendDetailKkoMydoc row : list) {

                ArrayList<String> commonCategories = (ArrayList<String>) Arrays.stream(Optional.ofNullable(row.getCommonCategories())
                                .orElseGet(() -> "")
                                .split(","))
                        .map(str -> str.trim())
                        .collect(Collectors.toList());


                Map<String, Object> receiver = new HashMap<>();
                if (CmmnUtil.isEmpty(row.getRecvCi())) {
                    receiver.put("phone_number", row.getRecvPhoneNumber());
                    receiver.put("name", row.getRecvName());
                    receiver.put("birthday", row.getRecvBirthday());
                    receiver.put("is_required_verify_name", row.getRecvIsRequireVfyName());
                } else {
                    receiver.put("ci", row.getRecvCi());
                }


                Map<String, Object> property = new HashMap<>();
//                property.put("link", CmmnUtil.isEmpty(row.getPropLink()) ? ("Y".equals(row.getMkBillUseYn()) ? row.getKkoBillMast().getUrl() : null) : row.getPropLink());
                property.put("link", row.getPropLink());
                property.put("payload", row.getPropPayload());
                property.put("message", row.getPropMessage());
                property.put("cs_number", row.getPropCsNumber());
                property.put("cs_name", row.getPropCsName());
                property.put("external_document_uuid", row.getPropExternalDocumentUuid());


                Map<String, Object> bridge = null;
                try {
                    if (!CmmnUtil.isEmpty(row.getBridge()))
                        bridge = gson.fromJson(row.getBridge(), Map.class);
                } catch (Exception e) {
                    throw new EnsException(EnsErrCd.SEND411, String.format("bridge 데이터 변환에 실패 했습니다. [ sendDetailId %s bridge %s ]", row.getSendDetailId(), row.getBridge()));
                }



                Map<String, Object> document = new HashMap<>();
                document.put("title", row.getTitle());
                document.put("hash", row.getHash());
                document.put("common_categories", commonCategories);
                if (CmmnUtil.isEmpty(row.getReadExpireadAt()))
                    document.put("read_expired_sec", row.getReadExpiredSec());
                else
                    document.put("read_expired_at", row.getReadExpireadAt());
                document.put("receiver", receiver);
                document.put("property", property);
                document.put("bridge", bridge);


                documents.add(document);
            }
            Map<String, Object> mJson = new HashMap<>();
            mJson.put("documents", documents);


            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.SEND502)
                    .errMsg(String.format("발송메시지 생성 중 오류 발생. %s", e.getMessage()))
                    .build();
        }

        return resp;
    }


    /**
     * 전송요청 응답결과를 API응답VO 객체에 매핑하여 반환
     *
     * @param row
     * @return
     */
    private Map<String, Object> toApiRespVOMap(Map<String, Object> row) {
        KkoMydocApiRespVO apiRespVO = 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("document_binder_uuid") ? String.valueOf(row.get("document_binder_uuid")) : null)
                .build();

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

    /**
     * 전송요청결과 갱신(update)
     * -. 성공/실패 여부에 따라 문서식별번호(document_binder_uuid) 및 Error 필드 갱신
     *
     * @param row
     * @param mApiRespVOByExtDocUuid
     */
    private void modifyDocSendRsltByExtDocUuid(SendDetailKkoMydoc row, Map<String, KkoMydocApiRespVO> mApiRespVOByExtDocUuid) {
        KkoMydocApiRespVO apiRespVO = mApiRespVOByExtDocUuid.get(row.getPropExternalDocumentUuid());
        if (CmmnUtil.isEmpty(apiRespVO.getError_code())) {
            row.setKkoDocStat(KkoMydocStatusCd.SENT);
            row.setDocumentBinderUuid(String.valueOf(apiRespVO.getData()));
            row.setError(FieldError.initBuilder().build());
        } else {
            row.setKkoDocStat(KkoMydocStatusCd.valueOfEnum(apiRespVO.getError_code()));
            row.setError(FieldError.initBuilder()
                    .errorCode(EnsErrCd.SEND630.getCode())
                    .errorMessage(String.format("%s %s", apiRespVO.getError_code(), apiRespVO.getError_message()))
                    .build());
        }

    }


    private Map<String, String> toResultInfo(SendDetailKkoMydoc row) {
        Map<String, String> m = new HashMap<>();
//        m.put("msgIdx", row.getMsgIdx());
        m.put("documentBinderUuid", row.getDocumentBinderUuid());
        m.put("externalDocumentUuid", row.getPropExternalDocumentUuid());
        if (!CmmnUtil.isEmpty(row.getError())) {
            m.put("errCd", row.getError().getErrorCode());
            m.put("errMsg", row.getError().getErrorMessage());
        }
        return m;
    }


    /**
     * 전송요청응답을 전송요청이력 Entity 데이터로 변환하여 반환 한다.
     *
     * @param sendMsg
     * @param respMsg
     * @return
     */
    private List<SendDetailKkoMydocReqHist> toSendDetailReqHist(List<SendDetailKkoMydoc> list, String sendMsg, String respMsg) {


        List<Map<String, Object>> sendMsgList = (List<Map<String, Object>>) gson.fromJson(sendMsg, Map.class).get("documents");
        Map<String, Long> mSendDetailIds = list.stream()
                .map(row -> {
                    Map<String, Object> m = new HashMap<>();
                    m.put("key", row.getPropExternalDocumentUuid());
                    m.put("value", row.getSendDetailId());
                    return m;
                })
                .collect(Collectors.toMap(m -> String.valueOf(m.get("key")), m -> (Long) m.get("value"), (k1, k2) -> k1));

        EnsResponseVO respVO = this.respMsgToMap(respMsg);


        if (EnsErrCd.OK.equals(respVO.getErrCode())) {
            Map<String, Object> mRespBody = (Map<String, Object>) respVO.getResultInfo();
            List<Map<String, String>> documents = (List<Map<String, String>>) mRespBody.get("documents");
            Map<String, Map<String, String>> mRespMsg = documents.stream()
                    .map(row -> {
                        Map<String, Object> m = new HashMap<>();
                        m.put("key", String.valueOf(row.get("external_document_uuid")));
                        Map<String, String> mValue = new HashMap<>();
                        mValue.put("document_binder_uuid", row.get("document_binder_uuid"));
                        mValue.put("error_code", row.get("error_code"));
                        mValue.put("error_message", row.get("error_message"));
                        mValue.put("rawMsg", gson.toJson(row));
                        m.put("value", mValue);
                        return m;
                    })
                    .collect(Collectors.toMap(m -> String.valueOf(m.get("key")), m -> (Map<String, String>) m.get("value"), (k1, k2) -> k1));

            return sendMsgList.stream()
                    .map(row -> {
                        Map<String, String> property = (Map<String, String>) row.get("property");
                        String externalDocumentUuid = property.get("external_document_uuid");
                        boolean isSuccess = CmmnUtil.isEmpty(mRespMsg.get(externalDocumentUuid).get("error_code"));

                        return SendDetailKkoMydocReqHist.builder()
                                .sendDetailId(mSendDetailIds.get(externalDocumentUuid))
                                .sendRawMsg(gson.toJson(row))
                                .externalDocumentUuid(externalDocumentUuid)
                                .documentBinderUuid(mRespMsg.get(externalDocumentUuid).get("document_binder_uuid"))
                                .respRawMsg(mRespMsg.get(externalDocumentUuid).get("rawMsg"))
                                .error(FieldError.initBuilder()
                                        .errorCode(isSuccess ? null : EnsErrCd.SEND620.getCode())
                                        .errorMessage(isSuccess ? null : String.format("%s %s"
                                                , mRespMsg.get(externalDocumentUuid).get("error_code")
                                                , mRespMsg.get(externalDocumentUuid).get("error_message")
                                        ))
                                        .build())
                                .build();
                    })
                    .collect(Collectors.toList());

        } else {
            return sendMsgList.stream()
                    .map(row -> {
                        Map<String, String> property = (Map<String, String>) row.get("property");
                        String externalDocumentUuid = property.get("external_document_uuid");
                        return SendDetailKkoMydocReqHist.builder()
                                .sendDetailId(mSendDetailIds.get(externalDocumentUuid))
                                .sendRawMsg(gson.toJson(row))
                                .externalDocumentUuid(externalDocumentUuid)
                                .respRawMsg(respMsg)
                                .error(FieldError.initBuilder()
                                        .errorCode(respVO.getErrCode().getCode())
                                        .errorMessage(respVO.getErrMsg())
                                        .build())
                                .build();
                    })
                    .collect(Collectors.toList());
        }
    }

    private EnsResponseVO respMsgToMap(String respMsg) {
        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.SEND620, String.format("전송요청API 오류. documents 키값이 없습니다. %s", respMsg));
        } catch (EnsException e) {
            errCode = e.getErrCd();
            errMsg = e.getMessage();
        } catch (Exception e) {
            errCode = EnsErrCd.SEND511;
            errMsg = String.format("전송요청API 응답데이터 JSON 객체 변환 실패. %s", respMsg);
        }
        return EnsErrCd.OK.equals(errCode) ?
                EnsResponseVO.okBuilder().resultInfo(mResp).build()
                : EnsResponseVO.errBuilder()
                .errCode(errCode)
                .errMsg(errMsg)
                .build();
    }
}
