package cokr.xit.ens.modules.kkomydoc.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.kkomydoc.domain.SendDetailKkoMydocTokenHist;
import cokr.xit.ens.modules.kkomydoc.domain.repository.SendDetailKkoMydocTokenHistRepository;
import cokr.xit.ens.modules.kkomydoc.model.KkoMydocAcceptReqDTO;
import cokr.xit.ens.modules.kkomydoc.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 KkoMydocService {
    private final KkoMydocAcceptor kkoMydocAcceptor;
//    private final KkoMydocMakerOfBillLink kkoMydocMakerOfBillLink;

    private final KkoMydocMaker kkoMydocMaker;
    private final KkoMydocSender kkoMydocSender;
    private final KkoMydocRsltFetcher kkoMydocRsltFetcher;
    private final KkoMydocRsltProvider kkoMydocRsltProvider;

    private final KkoMydocApiSpec kkoMydocApi;

    private final SendMastRepository sendMastRepository;
    private final SendDetailKkoMydocTokenHistRepository sendDetailKkoMydocTokenHistRepository;
    private final OrgMngService orgMngService;


    /**
     * 접수 서비스
     *
     * @param reqDTO
     * @return
     */
    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public EnsResponseVO accept(KkoMydocAcceptReqDTO reqDTO) {
        EnsResponseVO responseVO = null;
        try {
            responseVO = kkoMydocAcceptor.statBegin(reqDTO);
            if (!EnsErrCd.OK.equals(responseVO.getErrCode()))
                return responseVO;
            
            responseVO = kkoMydocAcceptor.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.kkoMydoc.equals(sendMast.getPostSe()))
                throw new EnsException(EnsErrCd.SEND404, String.format("%s 자료가 아닙니다.", PostSeCd.kkoMydoc.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();
        }

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


        return this.make(sendMastId);
    }

    /**
     * (일괄)제작 서비스
     *
     * @return
     */
    @Transactional(propagation = Propagation.SUPPORTS)
    public EnsResponseVO makeAll() {
        List<Long> sendMastIds = sendMastRepository.findAllByPostSeAndStatCdIn(PostSeCd.kkoMydoc, 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();

        kkoMydocMaker.statReady(sendMastIds);

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

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

    private EnsResponseVO make(Long sendMastId) {
        EnsResponseVO responseVO = kkoMydocMaker.statBegin(sendMastId);
        if (!EnsErrCd.OK.equals(responseVO.getErrCode()))
            return responseVO;

        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 = kkoMydocMaker.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 = kkoMydocMakerOfBillLink.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.kkoMydoc.equals(sendMast.getPostSe()))
                throw new EnsException(EnsErrCd.SEND404, String.format("%s 자료가 아닙니다.", PostSeCd.kkoMydoc.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 = kkoMydocSender.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.kkoMydoc, 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 = kkoMydocSender.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 -> {
                    kkoMydocSender.statBegin(sendMastId);
                    return sendMastId;
                })
                .map(sendMastId -> kkoMydocSender.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.kkoMydoc.equals(sendMast.getPostSe()))
                throw new EnsException(EnsErrCd.RSLT404, String.format("%s 자료가 아닙니다.", PostSeCd.kkoMydoc.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.kkoMydoc, 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 -> kkoMydocRsltFetcher.execute(sendMastId))
                .collect(Collectors.toList());

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


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

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

            ResponseEntity<String> resp = kkoMydocApi.token(orgMng.getKkoMdAccessToken(), documentBinderUuid, token);
            if (resp.getStatusCode() != HttpStatus.OK)  
                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("token_status");
            if ("USED".equals(tokenUsedAt)) {
                responseVO = EnsResponseVO.okBuilder()
                        .resultInfo(mResponse)
                        .build();
            } else { 
                String errorCode = (String) mResponse.get("error_code");
                String errorMessage = (String) mResponse.get("error_message");
                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();
                sendDetailKkoMydocTokenHistRepository.save(SendDetailKkoMydocTokenHist.builder()
                        .externalDocumentUuid(externalDocumentUuid)
                        .documentBinderUuid(documentBinderUuid)
                        .token(token)
                        .tokenStatus((String) resultInfo.get("token_status"))
                        .tokenUsedAt((Long) resultInfo.get("token_used_at"))
                        .docBoxSentAt((Long) resultInfo.get("doc_box_sent_at"))
                        .docBoxReceivedAt((Long) resultInfo.get("doc_box_received_at"))
                        .authenticatedAt((Long) resultInfo.get("authenticated_at"))
                        .userNotifiedAt((Long) resultInfo.get("user_notified_at"))
                        .payload((String) resultInfo.get("payload"))
                        .signedAt((String) resultInfo.get("signed_at"))
                        .build());
            } else {
                sendDetailKkoMydocTokenHistRepository.save(SendDetailKkoMydocTokenHist.builder()
                        .externalDocumentUuid(externalDocumentUuid)
                        .documentBinderUuid(documentBinderUuid)
                        .token(token)
                        .error(FieldError.initBuilder()
                                .errorCode(responseVO.getErrCode().getCode())
                                .errorMessage(responseVO.getErrMsg())
                                .build())
                        .build());
            }
        }

        return responseVO;
    }


    /**
     * 문서상태변경 서비스
     * -.문서열람 시 본 서비스를 호출하지 않으면 "최초열람시간(doc_box_read_at)"은 제공되지 않는다.
     *
     * @param documentBinderUuid
     * @return
     */
    @Transactional(propagation = Propagation.SUPPORTS)
    public EnsResponseVO readCmplt(String orgCd, String documentBinderUuid) {
        EnsResponseVO responseVO = null;

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

            ResponseEntity<String> resp = kkoMydocApi.readCompleted(orgMng.getKkoMdAccessToken(), documentBinderUuid);


            if (resp.getStatusCode().equals(HttpStatus.NO_CONTENT)) {
                responseVO = EnsResponseVO.okBuilder().build();
            } else {
                Map<String, Object> mResponse = null;
                try {  
                    Gson gson = new GsonBuilder().disableHtmlEscaping().create();
                    mResponse = gson.fromJson(resp.getBody(), Map.class);
                } catch (Exception e) {
                    throw new EnsException(EnsErrCd.ERR505, String.format("문서상태변경 응답데이터 파싱 실패. %s", resp.getBody()));
                }
                String errorCode = (String) mResponse.get("error_code");
                String errorMessage = (String) mResponse.get("error_message");
                responseVO = EnsResponseVO.errBuilder()
                        .errCode(EnsErrCd.ERR600)
                        .errMsg(String.format("%s %s", errorCode, errorMessage))
                        .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();
        }

        return responseVO;
    }


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

        return kkoMydocRsltProvider.execute(sendMastId);
    }


}
