package cokr.xit.ens.modules.ktsigntalk.gibis.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.model.OrgMngDTO;
import cokr.xit.ens.modules.common.ctgy.sys.mng.model.struct.OrgMngMapper;
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.ktsigntalk.gibis.domain.SendDetailKtGibisTokenHist;
import cokr.xit.ens.modules.ktsigntalk.gibis.domain.repository.SendDetailKtGibisRepository;
import cokr.xit.ens.modules.ktsigntalk.gibis.domain.repository.SendDetailKtGibisTokenHistRepository;
import cokr.xit.ens.modules.ktsigntalk.gibis.model.KtGibisAcceptReqDTO;
import cokr.xit.ens.modules.ktsigntalk.gibis.model.KtGibisRsltStatFwdDTO;
import cokr.xit.ens.modules.ktsigntalk.gibis.service.support.*;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.mapstruct.factory.Mappers;
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 KtGibisService {
    private final KtGibisAcceptor ktGibisAcceptor;

    private final KtGibisMaker ktGibisMaker;
    private final KtGibisSender ktGibisSender;
    private final KtGibisRsltFetcher ktGibisRsltFetcher;
    private final KtGibisRsltProvider ktGibisRsltProvider;

    private final KtGibisApi ktGibisApi;

    private final SendMastRepository sendMastRepository;
    private final SendDetailKtGibisRepository sendDetailKtGibisRepository;
    private final SendDetailKtGibisTokenHistRepository sendDetailKtGibisTokenHistRepository;
    private final OrgMngService orgMngService;


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

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


        return this.make(sendMastId);
    }

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

        ktGibisMaker.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 = ktGibisMaker.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 = ktGibisMaker.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;
    }


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

        try {
            SendMast sendMast = sendMastRepository.findById(sendMastId)
                    .orElseThrow(() -> new EnsException(EnsErrCd.SEND404, "일치하는 자료가 없습니다."));
            if (!PostSeCd.ktGibis.equals(sendMast.getPostSe()))
                throw new EnsException(EnsErrCd.SEND404, String.format("%s 자료가 아닙니다.", PostSeCd.ktGibis.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 = ktGibisSender.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.ktGibis, 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 = ktGibisSender.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 -> {
                    ktGibisSender.statBegin(sendMastId);
                    return sendMastId;
                })
                .map(sendMastId -> ktGibisSender.execute(sendMastId))
                .collect(Collectors.toList());

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


    /**
     * (대량)문서상태조회 서비스
     *
     * @return
     */
    @Transactional(propagation = Propagation.SUPPORTS)
    public EnsResponseVO statBulk(KtGibisRsltStatFwdDTO dto) {
//        return this.stat(dto);
        return (EnsResponseVO) this.stat(dto).getResultInfo();
    }

    /**
     * (대량)문서상태조회 서비스-일괄
     *
     * @return
     */
    @Transactional(propagation = Propagation.SUPPORTS)
    public EnsResponseVO statBulkAll() {

        return EnsResponseVO.errBuilder().errCode(EnsErrCd.RSLT512).errMsg(EnsErrCd.RSLT512.getCodeNm()).build();
    }

    private EnsResponseVO stat(KtGibisRsltStatFwdDTO dto) {
        EnsResponseVO resultInfo = ktGibisRsltFetcher.execute(dto);

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


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

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

            ResponseEntity<String> resp = ktGibisApi.token(orgMng.getKtStAccessToken(), orgMng.getKtStServiceCd(), srcKey, 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("result_cd");
            if ("00".equals(tokenUsedAt)) {
                responseVO = EnsResponseVO.okBuilder()
                        .resultInfo(mResponse)
                        .build();
            } else { 
                String errorCode = (String) mResponse.get("result_cd");
                String errorMessage = ((List<Map<String, Object>>) mResponse.get("errors"))
                        .stream()
                        .map(o -> o.get("error_msg"))
                        .collect(Collectors.toList()).toString();
                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();
                sendDetailKtGibisTokenHistRepository.save(SendDetailKtGibisTokenHist.builder()
                        .serviceCd(serviceCd)
                        .srcKey(srcKey)
                        .token(token)
                        .build());
            } else {
                sendDetailKtGibisTokenHistRepository.save(SendDetailKtGibisTokenHist.builder()
                        .serviceCd(serviceCd)
                        .srcKey(srcKey)
                        .token(token)
                        .error(FieldError.initBuilder()
                                .errorCode(responseVO.getErrCode().getCode())
                                .errorMessage(responseVO.getErrMsg())
                                .build())
                        .build());
            }
        }

        return responseVO;
    }

    /**
     * 문서상태변경 서비스
     *
     * @param orgCd
     * @param token
     * @return
     */
    @Transactional(propagation = Propagation.SUPPORTS)
    public EnsResponseVO readCmplt(String orgCd, String token) {
        EnsResponseVO responseVO = null;

        try {
            if (CmmnUtil.isEmpty(orgCd))
                throw new EnsException(EnsErrCd.ERR401, "기관코드(은)는 필수조건 입니다.");
            if (CmmnUtil.isEmpty(token))
                throw new EnsException(EnsErrCd.ERR401, "원타임토큰(은)는 필수조건 입니다.");
            OrgMng orgMng = orgMngService.find(orgCd).getResultInfo();

            ResponseEntity<String> resp = ktGibisApi.readCompleted(orgMng.getKtStAccessToken(), token, orgMng.getKtStServiceCd());
            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("result_cd");
            if ("00".equals(tokenUsedAt)) {
                responseVO = EnsResponseVO.okBuilder()
                        .resultInfo(mResponse)
                        .build();
            } else { 
                String errorCode = (String) mResponse.get("result_cd");
                String errorMessage = ((List<Map<String, Object>>) mResponse.get("errors"))
                        .stream()
                        .map(o -> o.get("error_msg"))
                        .collect(Collectors.toList()).toString();
                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();
        }

        return responseVO;
    }


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

        return ktGibisRsltProvider.execute(sendMastId);
    }


    /**
     * 인증토큰 갱신
     *
     * @param orgCd
     * @param clientId
     * @param clientSecret
     * @return
     */
    public EnsResponseVO refreshAuthToken(String orgCd, String clientId, String clientSecret) {

        EnsResponseVO responseVO = null;
        try {
            ResponseEntity<String> resp = ktGibisApi.refreshAccessToken(clientId, clientSecret);
            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()));
            }
            if (!CmmnUtil.isEmpty(mResponse.get("error")))
                throw new EnsException(EnsErrCd.ERR620, String.format("%s %s. message: %s", mResponse.get("status"), mResponse.get("error"), mResponse.get("message")));
            String accessToken = (String) mResponse.get("access_token");
//            String tokenType = (String) mResponse.get("token_type");
//            String expiresIn = (String) mResponse.get("expires_in");
//            String scope = (String) mResponse.get("scope");

            OrgMng orgMng = orgMngService.find(orgCd).getResultInfo();
            OrgMngMapper orgMngMapper = Mappers.getMapper(OrgMngMapper.class);
            OrgMngDTO orgMngDTO = orgMngMapper.toDto(orgMng);
            orgMngDTO.setKtStAccessToken(accessToken);
            orgMngService.modify(orgMngMapper.toDto(orgMng));

            responseVO = EnsResponseVO.okBuilder().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();

        }

        return responseVO;
    }

}
