package cokr.xit.ens.modules.kkotalk.service.support;

import java.util.*;
import java.util.stream.*;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.*;
import org.springframework.http.*;
import org.springframework.stereotype.*;
import org.springframework.transaction.annotation.*;

import com.google.common.collect.*;
import com.google.gson.*;

import cokr.xit.ens.core.aop.*;
import cokr.xit.ens.core.exception.*;
import cokr.xit.ens.core.exception.code.*;
import cokr.xit.ens.core.utils.*;
import cokr.xit.ens.modules.common.biztmplt.*;
import cokr.xit.ens.modules.common.code.*;
import cokr.xit.ens.modules.common.ctgy.sys.mng.domain.*;
import cokr.xit.ens.modules.common.ctgy.sys.mng.service.*;
import cokr.xit.ens.modules.common.domain.*;
import cokr.xit.ens.modules.common.domain.repository.*;
import cokr.xit.ens.modules.common.domain.support.*;
import cokr.xit.ens.modules.common.event.*;
import cokr.xit.ens.modules.kkotalk.code.*;
import cokr.xit.ens.modules.kkotalk.domain.*;
import cokr.xit.ens.modules.kkotalk.domain.repository.*;
import cokr.xit.ens.modules.kkotalk.mapper.*;
import cokr.xit.ens.modules.kkotalk.model.*;
import lombok.*;
import lombok.extern.slf4j.*;

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

    private final ApplicationEventPublisher applicationEventPublisher;

    private final SendMastRepository sendMastRepository;
    private final IKkoTalkMapper talkMapper;
    private final SendDetailKkoTalkStatHistRepository sendDetailKkoTalkStatHistRepository;
    private final OrgMngService orgMngService;
    private final KkoTalkApiService kkoTalkApi;
    @Value("${contract.kakao.talk.bulksend-batch-unit}")
    private int SEND_BATCH_UNIT;

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


        SendMast sendMast = null;
        List<SendDetailKkoTalkDTO> 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 = talkMapper.findAllBySendMastAndEnvlopeIdIsNotNull(sendMast);
            OrgMng orgMng = orgMngService.find(sendMast.getOrgCd()).getResultInfo();

            Lists.partition(sendDetails, SEND_BATCH_UNIT).stream()
                    .forEach(list -> {
                        List<String> envelopeIds = null;
                        String sendRespBody = null;

                        try {
                            envelopeIds = this.makeMessage(list);
                            ResponseEntity<String> resp = kkoTalkApi.findBulkStatus(
                                orgMng,
                                KkotalkDTO.BulkStatusRequest.builder()
                                    .envelopes(envelopeIds)
                                    .build()
                            );

                            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(sendRespBody);
                            if (!EnsErrCd.OK.equals(mResponse.getErrCode()))
                                throw new EnsException(mResponse.getErrCode(), mResponse.getErrMsg());

                            List<Map<String, Object>> envelopeStatus = (List<Map<String, Object>>) ((Map<String, Object>) mResponse.getResultInfo()).get("envelopeStatus");


                            Map<String, KkoTalkApiRespVO> mApiRespVO = envelopeStatus.stream()
                                .map(row -> this.toApiRespVOMap(row))
                                .collect(Collectors.toMap(m -> String.valueOf(m.get("key")), m -> (KkoTalkApiRespVO) m.get("value"), (k1, k2) -> k1));

                            list.forEach(row -> this.modifyStatInfoByEnvelopeId(row, mApiRespVO));

                        } catch (EnsException e) {

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

                            list.forEach(row -> row.setError(FieldError.initBuilder()
                                    .errorCode(EnsErrCd.RSLT500.getCode())
                                    .errorMessage(e.getMessage())
                                    .build())
                                );
                        } finally {
                            list
                                .forEach(talkMapper::updateKakaotalkStatusBulksResult);

                            if (!CmmnUtil.isEmpty(envelopeIds))
                                sendDetailKkoTalkStatHistRepository.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 -> KkoTalkStatusCd.READ.equals(row.getStatus()) ? 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 List<String> makeMessage(List<SendDetailKkoTalkDTO> list) {

        return list.stream()
                .map(SendDetailKkoTalkDTO::getEnvelopeId)
                .collect(Collectors.toList());
    }


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

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


    /**
     * 문서상태조회결과 갱신(update)
     * -. 성공/실패 여부에 따라 상태정보 및 Error 필드 갱신
     *
     */
    private void modifyStatInfoByEnvelopeId(SendDetailKkoTalkDTO row, Map<String, KkoTalkApiRespVO> resMap) {
        KkoTalkApiRespVO apiRespVO = resMap.get(row.getEnvelopeId());

        if (CmmnUtil.isEmpty(apiRespVO.getErrorCode())) {
            Map<String, Object> statusData = (Map<String, Object>) apiRespVO.getData();

            String status = statusData.containsKey("status") ? (String) statusData.get("status") : null;
            String sentAt = statusData.containsKey("sentAt") ? (String) statusData.get("sentAt") : null;
            String receivedAt = statusData.containsKey("receivedAt") ? (String) statusData.get("receivedAt") : null;
            String readAt = statusData.containsKey("readAt") ? (String) statusData.get("readAt") : null;
            String readExpiredAt = statusData.containsKey("readExpiredAt") ? (String) statusData.get("readExpiredAt") : null;
            String authenticatedAt = statusData.containsKey("authenticatedAt") ? (String) statusData.get("authenticatedAt") : null;
            String ottVerifiedAt = statusData.containsKey("ottVerifiedAt") ? (String) statusData.get("ottVerifiedAt") : null;
            String userNotifiedAt = statusData.containsKey("userNotifiedAt") ? (String) statusData.get("userNotifiedAt") : null;
            String distributionReceivedAt = statusData.containsKey("distributionReceivedAt") ? (String) statusData.get("distributionReceivedAt") : null;
            String payload = statusData.containsKey("payload") ? (String) statusData.get("payload") : null;

            row.setStatus(CmmnUtil.isEmpty(status) ? row.getStatus() : KkoTalkStatusCd.valueOfEnum(status));
            row.setSentAt(CmmnUtil.isEmpty(row.getSentAt()) ? sentAt : row.getSentAt());
            row.setReceivedAt(CmmnUtil.isEmpty(row.getReceivedAt()) ? receivedAt : row.getReceivedAt());
            row.setReadAt(CmmnUtil.isEmpty(row.getReadAt()) ? readAt : row.getReadAt());
            row.setReadExpiresAt(CmmnUtil.isEmpty(row.getReadExpiresAt()) ? readExpiredAt : row.getReadExpiresAt());
            row.setAuthenticatedAt(CmmnUtil.isEmpty(row.getAuthenticatedAt()) ? authenticatedAt : row.getAuthenticatedAt());
            row.setOttVerifiedAt(CmmnUtil.isEmpty(row.getOttVerifiedAt()) ? ottVerifiedAt : row.getOttVerifiedAt());
            row.setUserNotifiedAt(CmmnUtil.isEmpty(row.getUserNotifiedAt()) ? userNotifiedAt : row.getUserNotifiedAt());
            row.setDistributionReceivedAt(CmmnUtil.isEmpty(row.getDistributionReceivedAt()) ? distributionReceivedAt : row.getDistributionReceivedAt());
            row.setPayload(CmmnUtil.isEmpty(row.getPayload()) ? payload : row.getPayload());

            row.setError(FieldError.initBuilder().build());
        } else {
            if ("NOT_FOUND".equals(apiRespVO.getErrorCode()) && "envelopeStatus를 찾을 수 없습니다.".equals(apiRespVO.getErrorMessage()))
                row.setStatus(KkoTalkStatusCd.INTERNAL_SENT_ERR);

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


    }

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

//        Map<String, KkoMydocApiRespVO> mMydocApiRespVOByExtDocUuid =

        EnsResponseVO respVO = this.respMsgToMap(respMsg);


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

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

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

            return list.stream()
                    .map(row -> {
                        KkoTalkApiRespVO apiRespVO = mRespVO.get(row.getEnvelopeId());
                        Map<String, Object> statusData = (Map<String, Object>) apiRespVO.getData();
                        return SendDetailKkoTalkStatHist.builder()
                                .sendDetailId(row.getSendDetailId())
                                .envelopeId(row.getEnvelopeId())
                                .externalId(row.getExternalId())
                                .status(statusData.containsKey("status") ? (String) statusData.get("status") : null)
                                .sentAt(statusData.containsKey("sentAt") ? (String) statusData.get("sentAt") : null)
                                .receivedAt(statusData.containsKey("receivedAt") ? (String) statusData.get("receivedAt") : null)
                                .readAt(statusData.containsKey("readAt") ? (String) statusData.get("readAt") : null)
                                .readExpiredAt(statusData.containsKey("readExpiredAt") ? (String) statusData.get("readExpiredAt") : null)
                                .authenticatedAt(statusData.containsKey("authenticatedAt") ? (String) statusData.get("authenticatedAt") : null)
                                .ottVerifiedAt(statusData.containsKey("ottVerifiedAt") ? (String) statusData.get("ottVerifiedAt") : null)
                                .userNotifiedAt(statusData.containsKey("userNotifiedAt") ? (String) statusData.get("userNotifiedAt") : null)
                                .distributionReceivedAt(statusData.containsKey("distributionReceivedAt") ? (String) statusData.get("distributionReceivedAt") : null)
                                .payload(statusData.containsKey("payload") ? (String) statusData.get("payload") : null)
                                .error(FieldError.initBuilder()
                                        .errorCode(apiRespVO.getErrorCode())
                                        .errorMessage(apiRespVO.getErrorMessage())
                                        .build())
                                .build();
                    })
                    .collect(Collectors.toList());

        } else {
            return list.stream()
                    .map(row -> {
                        return SendDetailKkoTalkStatHist.builder()
                                .sendDetailId(row.getSendDetailId())
                                .envelopeId(row.getEnvelopeId())
                                .externalId(row.getExternalId())
                                .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("envelopeStatus")))
                throw new EnsException(EnsErrCd.RSLT620, String.format("카카오톡 상태조회API 오류. envelopeStatus 키값이 없습니다. %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();
    }

}
