package cokr.xit.ens.modules.nvsigntalk.service;

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.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.nvsigntalk.domain.SendDetailNvSigntalkTokenHist;
import cokr.xit.ens.modules.nvsigntalk.domain.repository.SendDetailNvSigntalkTokenHistRepository;
import cokr.xit.ens.modules.nvsigntalk.model.NvSigntalkAcceptReqDTO;
import cokr.xit.ens.modules.nvsigntalk.service.support.*;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

import java.time.LocalDateTime;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.stream.Collectors;

@Slf4j
@Service
@RequiredArgsConstructor
public class NvSigntalkService {
    private final NvSigntalkAcceptor nvSigntalkAcceptor;
   
    private final NvSigntalkMaker nvSigntalkMaker;
    private final NvSigntalkSender nvSigntalkSender;
    private final NvSigntalkRsltFetcher nvSigntalkRsltFetcher;
    private final NvSigntalkRsltProvider nvSigntalkRsltProvider;

    private final NvSigntalkApiSpec nvSigntalkApi;

    private final SendMastRepository sendMastRepository;
    private final SendDetailNvSigntalkTokenHistRepository sendDetailNvSigntalkTokenHistRepository;
    private final OrgMngService orgMngService;


    /**
     * 접수 서비스
     *
     * @param reqDTO
     * @return
     */
    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public EnsResponseVO accept(NvSigntalkAcceptReqDTO reqDTO) {
        EnsResponseVO responseVO = null;
        try {
            responseVO = nvSigntalkAcceptor.statBegin(reqDTO);
            if (!EnsErrCd.OK.equals(responseVO.getErrCode()))
                return responseVO;
            
            responseVO = nvSigntalkAcceptor.execute(reqDTO);
        } catch (EnsException e) {
            responseVO = EnsResponseVO.errBuilder()
                    .errCode(e.getErrCd())
                    .errMsg(e.getMessage())
                    .build();
        }

        return responseVO;
    }


    /**
     * 제작 서비스
     *
     * @param sendMastId
     * @return
     */
    @Transactional(propagation = Propagation.SUPPORTS)
    public EnsResponseVO remake(Long sendMastId) {
        try {
            SendMast sendMast = sendMastRepository.findById(sendMastId)
                    .orElseThrow(() -> new EnsException(EnsErrCd.SEND404, "일치하는 자료가 없습니다."));
            if (!PostSeCd.nvSigntalk.equals(sendMast.getPostSe()))
                throw new EnsException(EnsErrCd.SEND404, String.format("%s 자료가 아닙니다.", PostSeCd.nvSigntalk.getCodeNm()));
            if (!(StatCd.accept.equals(sendMast.getStatCd())
                    || StatCd.makefail.equals(sendMast.getStatCd())))
                throw new EnsException(EnsErrCd.SEND404, "접수/제작실패(accept/makefail) 단계가 아닙니다.");
        } catch (EnsException e) {
            return EnsResponseVO.errBuilder()
                    .errCode(e.getErrCd())
                    .errMsg(e.getMessage())
                    .build();
        }

        nvSigntalkMaker.statReady(Arrays.asList(sendMastId));


        return this.make(sendMastId);
    }

    /**
     * (일괄)제작 서비스
     *
     * @return
     */
    @Transactional(propagation = Propagation.SUPPORTS)
    public EnsResponseVO makeAll() {
        List<Long> sendMastIds = sendMastRepository.findAllByPostSeAndStatCdIn(PostSeCd.nvSigntalk, Arrays.asList(StatCd.accept))
                .stream()
                .map(row -> row.getSendMastId())
                .collect(Collectors.toList());
        if (sendMastIds.size() < 1)
            return EnsResponseVO.errBuilder()
                    .errCode(EnsErrCd.MAKE404)
                    .errMsg("\"접수(accept)\" 상태인 자료가 없습니다")
                    .build();

        nvSigntalkMaker.statReady(sendMastIds);

        List<EnsResponseVO> resultInfo = sendMastIds.stream()
                .map(sendMastId -> this.make(sendMastId))
                .collect(Collectors.toList());

        return EnsResponseVO.okBuilder().resultInfo(resultInfo).build();
    }

    @Transactional(propagation = Propagation.SUPPORTS)
    public EnsResponseVO make(Long sendMastId) {
        EnsResponseVO responseVO = nvSigntalkMaker.statBegin(sendMastId);
        if (!EnsErrCd.OK.equals(responseVO.getErrCode()))
            return responseVO;

//        return nvSigntalkMaker.execute(sendMastId);
        Optional<SendMast> sendMast = sendMastRepository.findById(sendMastId);
        try {
//           
//            responseVO = this.makeBillLink(sendMastId);
//            if (!EnsErrCd.OK.equals(responseVO.getErrCode()))
//                throw new EnsException(responseVO.getErrCode(), responseVO.getErrMsg());
           
            responseVO = nvSigntalkMaker.execute(sendMastId);
            if (!EnsErrCd.OK.equals(responseVO.getErrCode()))
                throw new EnsException(responseVO.getErrCode(), responseVO.getErrMsg());
        } catch (EnsException e) {
            return EnsResponseVO.errBuilder()
                    .errCode(e.getErrCd())
                    .errMsg(e.getMessage())
                    .build();
        }

        return responseVO;
    }

//    /**
//     * 제작(청구서링크) 서비스
//     *
//     * @param sendMastId
//     * @return
//     */
//    @Transactional(propagation = Propagation.SUPPORTS)
//    public EnsResponseVO makeBillLink(Long sendMastId) {
//        EnsResponseVO responseVO = nvSigntalkMakerOfBillLink.execute(sendMastId);
//
//        return responseVO;
//    }


    /**
     * (대량)전송 서비스
     *
     * @return
     */
    @Transactional(propagation = Propagation.SUPPORTS)
    public EnsResponseVO sendBulk(Long sendMastId) {

        try {
            SendMast sendMast = sendMastRepository.findById(sendMastId)
                    .orElseThrow(() -> new EnsException(EnsErrCd.SEND404, "일치하는 자료가 없습니다."));
            if (!PostSeCd.nvSigntalk.equals(sendMast.getPostSe()))
                throw new EnsException(EnsErrCd.SEND404, String.format("%s 자료가 아닙니다.", PostSeCd.nvSigntalk.getCodeNm()));
            if (!StatCd.makeok.equals(sendMast.getStatCd()))
                throw new EnsException(EnsErrCd.SEND404, "제작성공(makeok) 단계가 아닙니다.");
        } catch (EnsException e) {
            return EnsResponseVO.errBuilder()
                    .errCode(e.getErrCd())
                    .errMsg(e.getMessage())
                    .build();
        }

        List<Long> sendMastIds = Arrays.asList(sendMastId);
        EnsResponseVO responseVO = nvSigntalkSender.statReady(sendMastIds);
        if (!EnsErrCd.OK.equals(responseVO.getErrCode()))
            return responseVO;

        return this.send(sendMastIds);
    }

    /**
     * (대량)전송 서비스-일괄
     *
     * @return
     */
    @Transactional(propagation = Propagation.SUPPORTS)
    public EnsResponseVO sendBulkAll() {
        List<Long> sendMastIds = sendMastRepository.findAllByPostSeAndStatCdInAndSendDtBefore(PostSeCd.nvSigntalk, Arrays.asList(StatCd.makeok), LocalDateTime.now())
                .stream()
                .map(row -> row.getSendMastId())
                .collect(Collectors.toList());
        if (sendMastIds.size() < 1)
            return EnsResponseVO.errBuilder()
                    .errCode(EnsErrCd.SEND404)
                    .errMsg("발송시간이 경과한 자료 중 \"제작성공(makeok)\" 상태인 자료가 없습니다")
                    .build();

        EnsResponseVO responseVO = nvSigntalkSender.statReady(sendMastIds);
        if (!EnsErrCd.OK.equals(responseVO.getErrCode()))
            return responseVO;

        return this.send(sendMastIds);
    }

    private EnsResponseVO send(List<Long> sendMastIds) {
        List<EnsResponseVO> resultInfo = sendMastIds.stream()
                .map(sendMastId -> {
                    nvSigntalkSender.statBegin(sendMastId);
                    return sendMastId;
                })
                .map(sendMastId -> nvSigntalkSender.execute(sendMastId))
                .collect(Collectors.toList());

        return EnsResponseVO.okBuilder().resultInfo(resultInfo).build();
    }


    /**
     * (대량)문서상태조회 서비스
     *
     * @return
     */
    @Transactional(propagation = Propagation.SUPPORTS)
    public EnsResponseVO statBulk(Long sendMastId) {
        try {
            SendMast sendMast = sendMastRepository.findById(sendMastId)
                    .orElseThrow(() -> new EnsException(EnsErrCd.RSLT404, "일치하는 자료가 없습니다."));
            if (!PostSeCd.nvSigntalk.equals(sendMast.getPostSe()))
                throw new EnsException(EnsErrCd.RSLT404, String.format("%s 자료가 아닙니다.", PostSeCd.nvSigntalk.getCodeNm()));
            if (!Arrays.asList(StatCd.sendok, StatCd.sendcmplt, StatCd.open).stream()
                    .anyMatch(statCd -> statCd.equals(sendMast.getStatCd())))
                throw new EnsException(EnsErrCd.RSLT404, "전송성공/전송완료/열람중(sendok/sendcmplt/open) 단계가 아닙니다.");
        } catch (EnsException e) {
            return EnsResponseVO.errBuilder()
                    .errCode(e.getErrCd())
                    .errMsg(e.getMessage())
                    .build();
        }
        List<EnsResponseVO> responseVOS = (List<EnsResponseVO>) this.stat(Arrays.asList(sendMastId)).getResultInfo();
        return responseVOS.get(0);
    }

    /**
     * (대량)문서상태조회 서비스-일괄
     *
     * @return
     */
    @Transactional(propagation = Propagation.SUPPORTS)
    public EnsResponseVO statBulkAll() {
        List<Long> sendMastIds = sendMastRepository.findAllByPostSeAndStatCdIn(PostSeCd.nvSigntalk, Arrays.asList(StatCd.sendok, StatCd.sendcmplt, StatCd.open))
                .stream()
                .map(row -> row.getSendMastId())
                .collect(Collectors.toList());
        if (sendMastIds.size() < 1)
            return EnsResponseVO.errBuilder()
                    .errCode(EnsErrCd.RSLT404)
                    .errMsg("\"전송성공/전송완료/열람중(sendok/sendcmplt/open)\" 상태인 자료가 없습니다")
                    .build();

        return this.stat(sendMastIds);
    }

    private EnsResponseVO stat(List<Long> sendMastIds) {
        List<EnsResponseVO> resultInfo = sendMastIds.stream()
                .map(sendMastId -> nvSigntalkRsltFetcher.execute(sendMastId))
                .collect(Collectors.toList());

        return EnsResponseVO.okBuilder().resultInfo(resultInfo).build();
    }


    /**
     * 토큰검증 서비스
     *
     * @param docId
     * @param nonce
     * @param clientDocId
     * @return
     */
    @Transactional
    public EnsResponseVO tokenVerify(String orgCd, String docId, String nonce, String clientDocId) {
        EnsResponseVO responseVO = null;

        try {
            if (CmmnUtil.isEmpty(docId))
                throw new EnsException(EnsErrCd.ERR401, "문서식별번호(은)는 필수조건 입니다.");
            if (CmmnUtil.isEmpty(nonce))
                throw new EnsException(EnsErrCd.ERR401, "토큰(은)는 필수조건 입니다.");
            OrgMng orgMng = orgMngService.find(orgCd).getResultInfo();

            ResponseEntity<String> resp = nvSigntalkApi.token(orgMng.getNvStXNaverClientId(), orgMng.getNvStXNaverClientSecret(), docId, nonce);
//            if(resp.getStatusCode() != HttpStatus.OK)  
            if (!(resp.getStatusCode() == HttpStatus.OK
                    || resp.getStatusCode() == HttpStatus.BAD_REQUEST
                    || resp.getStatusCode() == HttpStatus.INTERNAL_SERVER_ERROR))  
                throw new EnsException(EnsErrCd.ERR620, String.format("토큰검증 실패. 실패사유: %s %s", resp.getStatusCode().toString(), resp.getBody()));

            Map<String, Object> mResponse = null;
            try {
                Gson gson = new GsonBuilder().disableHtmlEscaping().registerTypeAdapter(Map.class, new MapDeserailizer()).serializeNulls().create();
                mResponse = gson.fromJson(resp.getBody(), Map.class);
            } catch (Exception e) {
                throw new EnsException(EnsErrCd.ERR505, String.format("토큰검증 응답데이터 파싱 실패. %s", resp.getBody()));
            }

            String tokenUsedAt = (String) mResponse.get("result");
            if ("success".equals(tokenUsedAt) || "succoess".equals(tokenUsedAt)) {
                responseVO = EnsResponseVO.okBuilder()
                        .resultInfo(mResponse)
                        .build();
            } else { 
                String errorCode = (String) mResponse.get("errorCode");
                String errorMessage = (String) mResponse.get("errorMsg");
                responseVO = EnsResponseVO.errRsltBuilder()
                        .errCode(EnsErrCd.ERR600)
                        .errMsg(String.format("%s %s", errorCode, errorMessage))
                        .resultInfo(mResponse)
                        .build();
            }


        } catch (EnsException e) {
            responseVO = EnsResponseVO.errBuilder()
                    .errCode(e.getErrCd())
                    .errMsg(e.getMessage())
                    .build();
        } catch (Exception e) {
            responseVO = EnsResponseVO.errBuilder()
                    .errCode(EnsErrCd.ERR999)
                    .errMsg(e.getMessage())
                    .build();
        } finally {
            if (EnsErrCd.OK.equals(responseVO.getErrCode())) {
                Map<String, Object> resultInfo = (Map<String, Object>) responseVO.getResultInfo();
                sendDetailNvSigntalkTokenHistRepository.save(SendDetailNvSigntalkTokenHist.builder()
                        .clientDocId(clientDocId)
                        .docId(docId)
                        .nonce(nonce)
                        .result((String) resultInfo.get("result"))
                        .sendTs((Long) resultInfo.get("sendTs"))
                        .recvTs((Long) resultInfo.get("recvTs"))
                        .authReqTs((Long) resultInfo.get("authReqTs"))
                        .authViewTs((Long) resultInfo.get("authViewTs"))
                        .authCompleteTs((Long) resultInfo.get("authCompleteTs"))
                        .expireTs((Long) resultInfo.get("expireTs"))
                        .orgNotifiedTs((Long) resultInfo.get("orgNotifiedTs"))
                        .build());
            } else {
                sendDetailNvSigntalkTokenHistRepository.save(SendDetailNvSigntalkTokenHist.builder()
                        .clientDocId(clientDocId)
                        .docId(docId)
                        .nonce(nonce)
                        .error(FieldError.initBuilder()
                                .errorCode(responseVO.getErrCode().getCode())
                                .errorMessage(responseVO.getErrMsg())
                                .build())
                        .build());
            }
        }

        return responseVO;
    }


    /**
     * 전송결과 제공
     *
     * @param sendMastId
     * @return
     */
    @Transactional(propagation = Propagation.SUPPORTS)
    public EnsResponseVO sendResultProvide(Long sendMastId) {

        return nvSigntalkRsltProvider.execute(sendMastId);
    }


    /**
     * ping 확인
     *
     * @return
     */
    public EnsResponseVO ping() {
        ResponseEntity<String> resp = nvSigntalkApi.ping();
        if (resp.getStatusCode() != HttpStatus.OK)  
            return EnsResponseVO.errBuilder()
                    .errCode(EnsErrCd.ERR600)
                    .errMsg(String.format("PING 요청 실패.. %s %s", resp.getStatusCode().toString(), resp.getBody()))
                    .build();

        if (!"pong".equals(resp.getBody()))
            return EnsResponseVO.errRsltBuilder()
                    .errCode(EnsErrCd.ERR600)
                    .errMsg(String.format("PING 응답 오류.. %s", resp.getStatusCode().toString()))
                    .resultInfo(resp.getBody())
                    .build();

        return EnsResponseVO.okBuilder().resultInfo(resp.getBody()).build();
    }


}
