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

import cokr.xit.ens.biz.iup.domain.*;
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.nv.service.process.code.NvEpCtgyIdCd;
import cokr.xit.ens.modules.common.ctgy.intgrnbill.nv.service.process.model.NvPayNoticeRespData;
import cokr.xit.ens.modules.common.ctgy.intgrnbill.nv.service.process.model.config.notice.ProductItem;
import cokr.xit.ens.modules.common.ctgy.intgrnbill.support.code.PaidTypeCd;
import cokr.xit.ens.modules.common.ctgy.intgrnbill.support.code.PayUseSysHisSeCd;
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.PayNoticeService;
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.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;

import java.util.*;

@Slf4j
@RequiredArgsConstructor
@Service
public class IupNvPayNoticeService implements PayNoticeService<Map<String, Object>, Map<String, Object>, PayApiRespDTO<NvPayNoticeRespData>> {

    private final JPAQueryFactory query;
    private final VKakaoNoticeInfoSnRepository vKakaoNoticeInfoSnRepository;
    private final VKakaoNoticeInfoRepository vKakaoNoticeInfoRepository;
    private final TbIupBillPayHistRepository tbIupBillPayHistRepository;
    private Gson gson = new Gson();
    @Value("${contract.naver.pay.easy.api.validate.host}")
    private String BILL_HOST;
    @Value("${contract.naver.pay.easy.api.validate.prepay}")
    private String BILL_PAYABLE_URL;

    private enum DataKeys {
        tbInputDataXit,
        nvPayNoticeRespData,
        responseJsonStr,
    }

    private enum PayDataKeys {
        billerUserKey,  
        userBirth,  
        amount, 
        payExpireDate,  
        detailPasngChrgeSm, 
        detailAdiPasngChrgeSm,  
    }

    @Override
    public Map<String, Object> getData(Map<String, Object> reqParam) {
        Map<String, Object> reqData = (Map<String, Object>) reqParam.getOrDefault("data", new HashMap<>());
        final String billerUserKey = (String) reqData.getOrDefault("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())) {   
            VKakaoNoticeInfoSn vKakaoNoticeInfoSn = vKakaoNoticeInfoSnRepository.findById(dataId)  
                    .orElseThrow(() -> new EnsException(EnsErrCd.NO_DATA_FOUND, "일치하는 단순미납부과 자료가 없습니다."));
            Optional.of(vKakaoNoticeInfoSn)
                    .map(entity -> toDataMap(entity))  
                    .map(payData -> validateData(reqParam, payData))  
                    .map(payData -> createRespData(tbInputDataXit, payData))
                    .map(kkoPayNoticeRespData -> loadRespData(data, kkoPayNoticeRespData))  
                    .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 -> toDataMap(entity))  
                    .map(map -> validateData(reqParam, map))  
                    .map(map -> createRespData(tbInputDataXit, map))
                    .map(kkoPayNoticeRespData -> loadRespData(data, kkoPayNoticeRespData))  
                    .orElseThrow(() -> new EnsException(EnsErrCd.FAILED_CREATE_RESPONSE_DATA, "강제징수 응답데이터 생성에 실패했습니다."))
            ;
        } else {
            throw new EnsException(EnsErrCd.INVALID_DATA, "위탁여부가 등록되지 않았습니다.");
        }
    }

    private Map<PayDataKeys, Object> toDataMap(VKakaoNoticeInfoSn vKakaoNoticeInfoSn) {
        Map<PayDataKeys, Object> payData = new HashMap<>();
        payData.put(PayDataKeys.billerUserKey, vKakaoNoticeInfoSn.getDataId()); 
        payData.put(PayDataKeys.userBirth, vKakaoNoticeInfoSn.getUserBirth());  
        payData.put(PayDataKeys.amount, vKakaoNoticeInfoSn.getAmount().intValue()); 
        payData.put(PayDataKeys.payExpireDate, vKakaoNoticeInfoSn.getPayExpireDate());  
        payData.put(PayDataKeys.detailPasngChrgeSm, vKakaoNoticeInfoSn.getDetailPasngChrgeSm());    
        payData.put(PayDataKeys.detailAdiPasngChrgeSm, vKakaoNoticeInfoSn.getDetailAdiPasngChrgeSm());  
        return payData;
    }

    private Map<PayDataKeys, Object> toDataMap(VKakaoNoticeInfo vKakaoNoticeInfo) {
        Map<PayDataKeys, Object> payData = new HashMap<>();
        payData.put(PayDataKeys.billerUserKey, vKakaoNoticeInfo.getEnfrcLevMastrId());  
        payData.put(PayDataKeys.userBirth, vKakaoNoticeInfo.getUserBirth());    
        payData.put(PayDataKeys.amount, Integer.parseInt(vKakaoNoticeInfo.getAmount()));    
        payData.put(PayDataKeys.payExpireDate, vKakaoNoticeInfo.getPayExpireDate());    
        payData.put(PayDataKeys.detailPasngChrgeSm, vKakaoNoticeInfo.getDetailPasngChrgeSm());  
        payData.put(PayDataKeys.detailAdiPasngChrgeSm, vKakaoNoticeInfo.getDetailAdiPasngChrgeSm());    
        return payData;
    }

    private Map<PayDataKeys, Object> validateData(Map<String, Object> reqParam, Map<PayDataKeys, Object> payData) {
        return payData;
    }


    private NvPayNoticeRespData createRespData(TbInputDataXit tbInputDataXit, Map<PayDataKeys, Object> payData) {
        NvEpCtgyIdCd productSe = NvEpCtgyIdCd.ETC_ETC;
        List<ProductItem> productItems = Arrays.asList(ProductItem.builder()
                .categoryType(productSe.getCategoryTypeCd())
                .categoryId(productSe.getCode().split("_")[1])
//                .uid(tbInputDataXit.getLinkedUuid())
                .uid(tbInputDataXit.getTbInputXit().getFsJob().getJobCd()) //2023.03.14 네이버페이 검수 결과 - 상품고유ID로 변경
                .name(tbInputDataXit.getTbInputXit().getFsJob().getJobName())
                .count(1)
                .build());
        Integer productCount = productItems.stream().map(productItem -> productItem.getCount()).reduce(Integer::sum).get();
        return NvPayNoticeRespData.builder()
                .productName(tbInputDataXit.getTbInputXit().getFsJob().getJobName())
                .productCount(productCount)
                .totalPayAmount((Integer) payData.get(PayDataKeys.amount))
                .taxScopeAmount(0)
                .taxExScopeAmount((Integer) payData.get(PayDataKeys.amount))
                .environmentDepositAmount(0)
                .productItems(productItems)
                .returnUrl(this.BILL_HOST + this.BILL_PAYABLE_URL)  
                .build();
    }

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

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


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

        } else {   
            String resCode = null;
            String message = null;
            switch (e.getErrCd()) {
                case NO_DATA_FOUND:
                    resCode = PayUseSysResCd.NOT_FOUND_PAYDATA.getCode();
                    message = PayUseSysResCd.NOT_FOUND_PAYDATA.getCodeNm();
                    break;
                default:
                    resCode = PayUseSysResCd.PAYDATA_FIND_FAILED.getCode();
                    message = PayUseSysResCd.PAYDATA_FIND_FAILED.getCodeNm();
                    break;
            }

            return PayApiRespDTO.<NvPayNoticeRespData>errBuilder()
                    .resCode(resCode)
                    .message(message)
                    .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<>());
        final String billerUserKey = (String) reqData.getOrDefault("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.payNotice)   
                .requestData(gson.toJson(reqParam))
                .responseData((String) data.get(DataKeys.responseJsonStr.name()))
                .error(FieldError.initBuilder()
                        .errorCode(errorCode)
                        .errorMessage(errorMessage)
                        .build())
                .build();
        tbIupBillPayHistRepository.save(tbIupBillPayHist);
    }
}
