package cokr.xit.ens.modules.kkoalimtalk.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.kkoalimtalk.code.AlimtalkResultCd;
import cokr.xit.ens.modules.kkoalimtalk.domain.SendDetailKkoAlimtalk;
import cokr.xit.ens.modules.kkoalimtalk.domain.SendDetailKkoAlimtalkReqHist;
import cokr.xit.ens.modules.kkoalimtalk.domain.repository.SendDetailKkoAlimtalkRepository;
import cokr.xit.ens.modules.kkoalimtalk.domain.repository.SendDetailKkoAlimtalkReqHistRepository;
import cokr.xit.ens.modules.kkoalimtalk.model.KkoAlimtalkApiRespVO;
import cokr.xit.ens.modules.kkoalimtalk.model.config.Attachment;
import cokr.xit.ens.modules.kkoalimtalk.model.config.MmsAttach;
import cokr.xit.ens.modules.kkoalimtalk.model.config.MsgInfo;
import cokr.xit.ens.modules.kkoalimtalk.model.config.Supplement;
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 KkoAlimtalkSender extends SendProcTemplate {

    private final ApplicationEventPublisher applicationEventPublisher;

    private final KkoAlimtalkApi kkoAlimtalkApi;
    private final SendMastRepository sendMastRepository;
    private final SendDetailKkoAlimtalkRepository sendDetailKkoAlimtalkRepository;
    private final SendDetailKkoAlimtalkReqHistRepository sendDetailKkoAlimtalkReqHistRepository;
    private final OrgMngService orgMngService;


    @Value("${contract.kakao.alimtalk.biztalk.api.send-batch-unit}")
    private int SEND_BATCH_UNIT;

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

    /**
     * 알림톡발송요청
     *
     * @param sendMastId
     * @return
     */
    @Transactional(propagation = Propagation.REQUIRES_NEW)
    @Override
    public EnsResponseVO<Map<String, Object>> execute(Long sendMastId) {

        SendMast sendMast = null;
        List<SendDetailKkoAlimtalk> sendDetails = null;
        EnsResponseVO<Map<String, Object>> 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 = sendDetailKkoAlimtalkRepository.findAllFetchBySendMastId(sendMast.getSendMastId());
            OrgMng orgMng = orgMngService.find(sendMast.getOrgCd()).getResultInfo();



            ResponseEntity<String> resp = kkoAlimtalkApi.token(orgMng.getKkoAtBsid(), orgMng.getKkoAtPasswd());
//            ResponseEntity<String> resp = kkoAlimtalkApi.tokenTest();
            if (resp.getStatusCode() != HttpStatus.OK)
                throw new EnsException(EnsErrCd.SEND620, String.format("토큰요청 중.. %s %s", resp.getStatusCode().toString(), resp.getBody()));
            Map<String, Object> mResponse = null;
            try {
                mResponse = gson.fromJson(resp.getBody(), Map.class);
            } catch (Exception e) {
                throw new EnsException(EnsErrCd.SEND511, String.format("토큰요청API 응답데이터 JSON 객체 변환 실패. %s", resp.getBody()));
            }
            if (!"1000".equals(mResponse.get("responseCode")))
                throw new EnsException(EnsErrCd.SEND601, String.format("%s %s", mResponse.get("responseCode"), mResponse.get("msg")));
            final String btToken = String.valueOf(mResponse.get("token"));


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

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

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


                            ResponseEntity<String> sendResp = kkoAlimtalkApi.sendBatch(btToken, jsonStr);
//                            ResponseEntity<String> sendResp = kkoAlimtalkApi.sendBatchTest(list);
                            if (log.isDebugEnabled()) {
                                StringBuffer sb = new StringBuffer();
                                sb.append("\n==============================================================================")
                                        .append("\n[ Kakao Alimtalk - Sending ]")
                                        .append("\n### Request Info...")
                                        .append("\n" + jsonStr)
                                        .append("\n### Response Info...")
                                        .append("\n" + sendResp.getBody())
                                        .append("\n==============================================================================");
                                log.debug(sb.toString());
                            }

                            sendRespBody = sendResp.getBody();
                            if (sendResp.getStatusCode() != HttpStatus.OK)
                                throw new EnsException(EnsErrCd.SEND620, String.format("전송요청 중.. %s %s", sendResp.getStatusCode().toString(), resp.getBody()));
                            KkoAlimtalkApiRespVO kkoAlimtalkSendApiRespVO = gson.fromJson(sendRespBody, KkoAlimtalkApiRespVO.class);


                            Map<String, MsgInfo> mApiRespVOByMsgIdx = this.toApiRespVOMap(kkoAlimtalkSendApiRespVO);

                            list.stream()
                                    .forEach(row -> this.modifyDocSendRsltByReqMsgIdx(row, mApiRespVOByMsgIdx));

                        } catch (EnsException e) {

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

                            list.stream()
                                    .forEach(row -> {
                                        row.setKkoResultCd(AlimtalkResultCd.SENT_FAIL);
                                        row.setError(FieldError.initBuilder()
                                                .errorCode(EnsErrCd.SEND500.getCode())
                                                .errorMessage(e.getMessage())
                                                .build());
                                    });
                        } finally {

                            if (!CmmnUtil.isEmpty(jsonStr))
                                sendDetailKkoAlimtalkReqHistRepository.saveAll(this.toSendDetailReqHist(list, btToken, 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) {
                Map<String, Object> resultInfo = new HashMap<>();
                resultInfo.put("btToken", btToken);
                resultInfo.put("sendMastId", sendMastId);
                respVO = EnsResponseVO.<Map<String, Object>>okBuilder().resultInfo(resultInfo).build();
            } else {
                throw new EnsException(EnsErrCd.SEND500, String.format("전체건수 전송 실패(총 %d 건)", sendDetails.size()));
            }
        } catch (EnsException e) {

            respVO = EnsResponseVO.<Map<String, Object>>errBuilder().errCode(e.getErrCd()).errMsg(e.getMessage()).build();
        } catch (Exception e) {

            respVO = EnsResponseVO.<Map<String, Object>>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.kkoAlimtalk)
                                    .statCd(StatCd.sendfail)
                                    .errCd(respVO.getErrCode())
                                    .message("-.SendMastId: " + sendMastId + "\n" + respVO.getErrMsg())
                                    .build())
                            .build());
                }


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

        return respVO;
    }


    private EnsResponseVO makeMessage(List<SendDetailKkoAlimtalk> list, String senderKey) {
        String result = null;
        EnsResponseVO resp = null;
        try {


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


                Map<String, Object> msg = new HashMap<>();
                msg.put("msgIdx", row.getReqMsgIdx());
                msg.put("countryCode", row.getCountryCode());
                msg.put("recipient", row.getRecipient());
                msg.put("senderKey", senderKey);
                msg.put("appUserId", row.getAppUserId());
                msg.put("orgCode", row.getOrgCode());
                msg.put("message", row.getMessage());
                msg.put("tmpltCode", row.getSendMast().getTmpltCd());
                msg.put("title", row.getTitle());
                msg.put("resMethod", row.getResMethod());
                msg.put("attach", CmmnUtil.isEmpty(row.getAttach()) ? null : gson.fromJson(row.getAttach(), Attachment.class));
                msg.put("supplement", this.supplementFromJson(row.getSupplement(), row.getSendDetailId()));
                msg.put("messageType", row.getMessageType());
                msg.put("useFallback", row.getUseFailback());
                if ("Y".equals(row.getUseFailback()))
                    msg.put("mmsAttach", this.mmsAttachFromJson(row.getMmsAttach(), row.getSendDetailId()));

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


            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;
    }


    private Map<String, MsgInfo> toApiRespVOMap(KkoAlimtalkApiRespVO respVO) {
        if (CmmnUtil.isEmpty(respVO) || CmmnUtil.isEmpty(respVO.getMsgList()))
            return new HashMap<>();

        return respVO.getMsgList().stream()
                .map(row -> {
                    Map<String, Object> m = new HashMap<>();
                    m.put("key", row.getMsgIdx());
                    m.put("value", row);
                    return m;
                })
                .collect(Collectors.toMap(m -> String.valueOf(m.get("key")), m -> (MsgInfo) m.get("value"), (k1, k2) -> k1));
    }

    private void modifyDocSendRsltByReqMsgIdx(SendDetailKkoAlimtalk row, Map<String, MsgInfo> mApiRespVOByMsgIdx) {
        MsgInfo apiRespVO = mApiRespVOByMsgIdx.get(row.getReqMsgIdx());

        AlimtalkResultCd resultCd = AlimtalkResultCd.valueOfEnum("RQ_" + apiRespVO.getResponseCode());
        if (AlimtalkResultCd.RQ_1000.equals(resultCd)) {
            row.setKkoResultCd(resultCd);
            row.setError(FieldError.initBuilder().build());
        } else {
            row.setKkoResultCd(resultCd);
            row.setError(FieldError.initBuilder()
                    .errorCode(EnsErrCd.SEND630.getCode())
                    .errorMessage(String.format("%s %s", apiRespVO.getResponseCode(), resultCd.getCodeNm()))
                    .build());
        }

    }


    private List<SendDetailKkoAlimtalkReqHist> toSendDetailReqHist(List<SendDetailKkoAlimtalk> list, String btToken, String sendMsg, String respMsg) {

        List<Map<String, Object>> sendMsgList = (List<Map<String, Object>>) gson.fromJson(sendMsg, Map.class).get("msgList");
        Map<String, Long> mSendDetailIds = list.stream()
                .map(row -> {
                    Map<String, Object> m = new HashMap<>();
                    m.put("key", row.getReqMsgIdx());
                    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>> respMsgList = (List<Map<String, String>>) mRespBody.get("msgList");
            Map<String, Object> mRespMsg = respMsgList.stream()
                    .map(row -> {
                        Map<String, Object> m = new HashMap<>();
                        m.put("key", String.valueOf(row.get("msgIdx")));
                        m.put("value", row);
                        return m;
                    })
                    .collect(Collectors.toMap(m -> String.valueOf(m.get("key")), m -> m.get("value"), (k1, k2) -> k1));

            return sendMsgList.stream()
                    .map(row -> {
                        String msgIdx = String.valueOf(row.get("msgIdx"));
                        Map<String, String> respRow = (Map<String, String>) mRespMsg.get(msgIdx);

                        return SendDetailKkoAlimtalkReqHist.builder()
                                .sendDetailId(mSendDetailIds.get(msgIdx))
                                .btToken(btToken)
                                .sendRawMsg(gson.toJson(row))
                                .responseCode(String.valueOf(mRespBody.get("responseCode")))
                                .msg(String.valueOf(mRespBody.get("msg")))
                                .reqMsgIdx(msgIdx)
                                .msgResponseCode(respRow.get("responseCode"))
                                .error(FieldError.initBuilder().build())
                                .build();
                    })
                    .collect(Collectors.toList());

        } else {
            return sendMsgList.stream()
                    .map(row -> {
                        String msgIdx = String.valueOf(row.get("msgIdx"));
                        return SendDetailKkoAlimtalkReqHist.builder()
                                .sendDetailId(mSendDetailIds.get(msgIdx))
                                .btToken(btToken)
                                .sendRawMsg(gson.toJson(row))
                                .reqMsgIdx(msgIdx)
                                .error(FieldError.initBuilder()
                                        .errorCode(respVO.getErrCode().getCode())
                                        .errorMessage(respVO.getErrMsg())
                                        .build())
                                .build();
                    })
                    .collect(Collectors.toList());
        }
    }


    private Supplement supplementFromJson(String supplement, Long sendDetailId) {
        if (CmmnUtil.isEmpty(supplement)) return null;

        try {
            return gson.fromJson(supplement, Supplement.class);
        } catch (Exception e) {
            throw new EnsException(EnsErrCd.SEND411, String.format("supplement JSON 객체 변환 실패(jsonStr -> Map). [ sendDetailId %s supplement %s ]", sendDetailId, supplement));
        }
    }

    private MmsAttach mmsAttachFromJson(String mmsAttach, Long sendDetailId) {
        if (CmmnUtil.isEmpty(mmsAttach)) return null;

        try {
            return gson.fromJson(mmsAttach, MmsAttach.class);
        } catch (Exception e) {
            throw new EnsException(EnsErrCd.SEND411, String.format("mmsAttach JSON 객체 변환 실패(jsonStr -> Map). [ sendDetailId %s supplement %s ]", sendDetailId, mmsAttach));
        }

    }


    private EnsResponseVO respMsgToMap(String respMsg) {
        EnsErrCd errCode = EnsErrCd.OK;
        String errMsg = EnsErrCd.OK.getCodeNm();
        Map<String, Object> mResp = gson.fromJson(respMsg, Map.class);
        try {
            mResp = gson.fromJson(respMsg, Map.class);
            if (!"1000".equals(mResp.get("responseCode")))
                throw new EnsException(EnsErrCd.SEND620, String.format("전송요청API 오류. %s %s", mResp.get("responseCode"), mResp.get("msg")));
        } 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();
    }
}
