package cokr.xit.ens.biz.iup.billpay.service.process;

import cokr.xit.ens.biz.iup.domain.*;
import cokr.xit.ens.biz.iup.domain.repository.TbEnfrcLevMastrRepository;
import cokr.xit.ens.biz.iup.domain.repository.TbIupBillPayHistRepository;
import cokr.xit.ens.biz.iup.domain.repository.VKakaoNoticeInfoRepository;
import cokr.xit.ens.biz.iup.domain.repository.VKakaoNoticeInfoSnRepository;
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.modules.common.ctgy.intgrnbill.support.api.PayUseSysApi;
import cokr.xit.ens.modules.common.ctgy.intgrnbill.support.code.PayUseSysHisSeCd;
import cokr.xit.ens.modules.common.ctgy.intgrnbill.support.code.PaidTypeCd;
import cokr.xit.ens.modules.common.ctgy.intgrnbill.support.code.PayUseSysResCd;
import cokr.xit.ens.modules.common.ctgy.intgrnbill.support.model.PayApiRespDTO;
import cokr.xit.ens.modules.common.ctgy.intgrnbill.support.service.process.kit.PayAbleService;
import cokr.xit.ens.modules.common.domain.support.FieldError;
import com.google.gson.Gson;
import com.querydsl.jpa.impl.JPAQueryFactory;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Service;

import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;

@Slf4j
@RequiredArgsConstructor
@Service
public class IupNvPayAbleService implements PayAbleService<Map<String, Object>, Map<String, Object>, PayApiRespDTO<Map<String, Object>>> {

    private final JPAQueryFactory query;
    private final VKakaoNoticeInfoSnRepository vKakaoNoticeInfoSnRepository;
    private final VKakaoNoticeInfoRepository vKakaoNoticeInfoRepository;
    private final TbEnfrcLevMastrRepository tbEnfrcLevMastrRepository;
    private final PayUseSysApi payUseSysApi;
    private final TbIupBillPayHistRepository tbIupBillPayHistRepository;
    private Gson gson = new Gson();

    private enum DataKeys {
        tbInputDataXit,
        payApiRespDTO,
        responseJsonStr,
    }

    @Override
    public Map<String, Object> getData(Map<String, Object> reqParam) {
        Map<String, Object> reqData = (Map<String, Object>) reqParam.getOrDefault("data", new HashMap<>());
        String billerUserKey = (String) reqData.get("billerUserKey");
//        String productName = (String) reqData.get("productName");
//        Integer totalPayAmount = (Integer) reqData.get("totalPayAmount");
//        Integer taxScopeAmount = (Integer) reqData.get("taxScopeAmount");
//        Integer taxExScopeAmount = (Integer) reqData.get("taxExScopeAmount");

        if (billerUserKey == null) billerUserKey = "";
        TbInputDataXit tbInputDataXit = query.selectFrom(QTbInputDataXit.tbInputDataXit)
                .innerJoin(QTbInputDataXit.tbInputDataXit.tbInputXit, QTbInputXit.tbInputXit).fetchJoin()
                .innerJoin(QTbInputXit.tbInputXit.fsJob, QFsJob.fsJob).fetchJoin()
                .where(QTbInputDataXit.tbInputDataXit.linkedUuid.eq(billerUserKey))
                .fetchOne();


        Map<String, Object> data = new HashMap<>();
        data.put(DataKeys.tbInputDataXit.name(), tbInputDataXit);
        return data;
    }

    @Override
    public void validate(Map<String, Object> reqParam, Map<String, Object> data) {
       
        if (CmmnUtil.isEmpty(data.get(DataKeys.tbInputDataXit.name())))
            throw new EnsException(EnsErrCd.NO_DATA_FOUND, EnsErrCd.NO_DATA_FOUND.getCodeNm());
    }

    @Override
    public void callApi(Map<String, Object> reqParam, Map<String, Object> data) {
        TbInputDataXit tbInputDataXit = (TbInputDataXit) data.get(DataKeys.tbInputDataXit.name());
        TbInputXit tbInputXit = tbInputDataXit.getTbInputXit();
        FsJob fsJob = tbInputXit.getFsJob();
        final String dataId = tbInputDataXit.getDataId();

        if ("Y".equals(fsJob.getBpEntrustYn()) || "y".equals(fsJob.getBpEntrustYn())) {   

            final String url = fsJob.getBpNvPrepayApi();    

            VKakaoNoticeInfoSn vKakaoNoticeInfoSn = vKakaoNoticeInfoSnRepository.findById(dataId)  
                    .orElseThrow(() -> new EnsException(EnsErrCd.NO_DATA_FOUND, "일치하는 단순미납부과 자료가 없습니다."));
            Optional.of(vKakaoNoticeInfoSn)
                    .map(entity -> payUseSysApi.payAble(url, reqParam, null))    
                    .map(response -> checkResponse(response))  
                    .map(payApiRespDTO -> loadRespData(data, payApiRespDTO))   
                    .orElseThrow(() -> new EnsException(EnsErrCd.FAILED_CREATE_RESPONSE_DATA, "단순미납 응답데이터 수신에 실패했습니다."))
            ;
        } else if ("N".equals(fsJob.getBpEntrustYn()) || "n".equals(fsJob.getBpEntrustYn())) {    
            if (dataId.length() < 20)
                throw new EnsException(EnsErrCd.INVALID_DATA, "데이터ID가 20자리 미만 입니다.");
            VKakaoNoticeInfo vKakaoNoticeInfo = vKakaoNoticeInfoRepository.findById(dataId.substring(0, 20))  
                    .orElseThrow(() -> new EnsException(EnsErrCd.NO_DATA_FOUND, "일치하는 강제징수부과 자료가 없습니다."));
            Optional.of(vKakaoNoticeInfo)
                    .map(entity -> validateData(reqParam, entity));  
            TbEnfrcLevMastr tbEnfrcLevMastr = tbEnfrcLevMastrRepository.findByEnfrcLevMastrIdAndEnfrcLevMastrProcessSttusAndDeleteAt(dataId.substring(0, 20), "04", "N")
                    .orElseThrow(() -> new EnsException(EnsErrCd.NO_DATA_FOUND, "납부가능 상태가 아닙니다.", PayUseSysResCd.NO_TARGET_PAYABLE));
            Optional.of(tbEnfrcLevMastr)
                    .map(entity -> createRespData())   
                    .map(payApiRespDTO -> loadRespData(data, payApiRespDTO))   
                    .orElseThrow(() -> new EnsException(EnsErrCd.FAILED_CREATE_RESPONSE_DATA, "강제징수 응답데이터 생성에 실패했습니다."));
        } else {
            throw new EnsException(EnsErrCd.INVALID_DATA, "위탁여부가 등록되지 않았습니다.");
        }

    }

    private PayApiRespDTO<Map<String, Object>> checkResponse(ResponseEntity<String> response) {
        
        if (!HttpStatus.OK.equals(response.getStatusCode()))
            throw new EnsException(EnsErrCd.API_COMM_ERROR, EnsErrCd.API_COMM_ERROR.getCodeNm() + " " + response.getStatusCode().toString(), response.getBody());
        
        PayApiRespDTO<Map<String, Object>> result = null;
        try {
            result = gson.fromJson(response.getBody(), PayApiRespDTO.class);
        } catch (Exception ex) {
            throw new EnsException(EnsErrCd.INVALID_RESPONSE, EnsErrCd.INVALID_RESPONSE.getCodeNm(), ex);
        }

        return result;
    }

    private PayApiRespDTO loadRespData(Map<String, Object> data, PayApiRespDTO payApiRespDTO) {
        if (CmmnUtil.isEmpty(payApiRespDTO))
            return null;

        data.put(DataKeys.payApiRespDTO.name(), payApiRespDTO);
        data.put(DataKeys.responseJsonStr.name(), gson.toJson(payApiRespDTO));
        return payApiRespDTO;
    }

    private VKakaoNoticeInfo validateData(Map<String, Object> reqParam, VKakaoNoticeInfo vKakaoNoticeInfo) {
        Map<String, Object> reqData = (Map<String, Object>) reqParam.getOrDefault("data", new HashMap<>());

        final String productName = (String) reqData.get("productName");
        final Integer totalPayAmount = (Integer) reqData.getOrDefault("totalPayAmount", 0);
        final Integer taxScopeAmount = (Integer) reqData.get("taxScopeAmount");
        final Integer taxExScopeAmount = (Integer) reqData.get("taxExScopeAmount");


        if (totalPayAmount != Integer.parseInt(vKakaoNoticeInfo.getAmount()))
            throw new EnsException(EnsErrCd.INVALID_DATA
                    , String.format("납부 가능 금액이 없거나 일치하지 않습니다. (요청금액:%s, 실제금액:%s)", totalPayAmount, vKakaoNoticeInfo.getAmount())
                    , PayUseSysResCd.NO_AMOUNT_PAYABLE);
        if (totalPayAmount > Integer.parseInt(vKakaoNoticeInfo.getAmount()))
            throw new EnsException(EnsErrCd.INVALID_DATA
                    , String.format("납부 가능 금액을 초과했습니다. (요청금액:%s, 실제금액:%s)", totalPayAmount, vKakaoNoticeInfo.getAmount())
                    , PayUseSysResCd.OVERPAYABLE_AMOUNT);
        if (CmmnUtil.isEmpty(vKakaoNoticeInfo.getPayExpireDate()))
            throw new EnsException(EnsErrCd.INVALID_DATA
                    , "납부기한의 값이 비어있습니다."
                    , PayUseSysResCd.EXPIRED_DATE);
        SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyyMMdd");
        if (Integer.parseInt(vKakaoNoticeInfo.getPayExpireDate()) < Integer.parseInt(simpleDateFormat.format(new Date())))
            throw new EnsException(EnsErrCd.INVALID_DATA
                    , "납부기한이 경과한 자료 입니다."
                    , PayUseSysResCd.EXPIRED_DATE);

        return vKakaoNoticeInfo;
    }

    private PayApiRespDTO<Map<String, Object>> createRespData() {
        return PayApiRespDTO.<Map<String, Object>>okBuilder().build();
    }

    @Override
    public PayApiRespDTO<Map<String, Object>> generateResult(Map<String, Object> reqParam, Map<String, Object> data, EnsException e) {
        if (e == null) {    
            PayApiRespDTO<Map<String, Object>> payApiRespDTO = (PayApiRespDTO<Map<String, Object>>) data.get(DataKeys.payApiRespDTO.name());
            return payApiRespDTO;

        } else {   
            if (e.getData() != null && e.getData() instanceof PayUseSysResCd) {
                PayUseSysResCd resCode = (PayUseSysResCd) e.getData();
                return PayApiRespDTO.<Map<String, Object>>errBuilder()
                        .resCode(resCode.getCode())
                        .message(resCode.getCodeNm())
                        .build();
            } else {
                return PayApiRespDTO.<Map<String, Object>>errBuilder()
                        .resCode(PayUseSysResCd.FAILED_CHECK_INFO.getCode())
                        .message(PayUseSysResCd.FAILED_CHECK_INFO.getCodeNm())
                        .build();
            }
        }
    }

    @Override
    public void saveHist(Map<String, Object> reqParam, Map<String, Object> data, EnsException e) {
        Map<String, Object> reqData = (Map<String, Object>) reqParam.getOrDefault("data", new HashMap<>());
        String billerUserKey = (String) reqData.get("billerUserKey");

        String errorCode = null;
        String errorMessage = null;
        if (e != null) {
            errorCode = e.getErrCd().getCode();
            errorMessage = String.format("%s %s", e.getMessage(), CmmnUtil.printStackTraceToString(e));
        }

        TbIupBillPayHist tbIupBillPayHist = TbIupBillPayHist.builder()
                .dataId(billerUserKey)
                .paidType(PaidTypeCd.naverPay)   
                .hisSeCd(PayUseSysHisSeCd.payAble)   
                .requestData(gson.toJson(reqParam))
                .responseData((String) data.get(DataKeys.responseJsonStr.name()))
                .error(FieldError.initBuilder()
                        .errorCode(errorCode)
                        .errorMessage(errorMessage)
                        .build())
                .build();
        tbIupBillPayHistRepository.save(tbIupBillPayHist);
    }

}
