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.kko.service.process.model.KkoPayNoticeRespData;
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.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.stereotype.Service;

import java.util.*;

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

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

    private enum DataKeys {
        tbInputDataXit,
        kkoPayNoticeRespData,
        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<>());
        String billerUserKey = (String) reqData.get("biller_user_key");

        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())) {   
            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) {
        Map<String, Object> reqData = (Map<String, Object>) reqParam.getOrDefault("data", new HashMap<>());

        String userBirth = (String) reqData.getOrDefault("user_birth", "");
        if (!userBirth.equals(payData.get(PayDataKeys.userBirth)))
            throw new EnsException(EnsErrCd.INVALID_DATA, String.format("요청과 실제 생년월일이 일치하지 않습니다. (요청: %s 실제: %s)", userBirth, payData.get(PayDataKeys.userBirth)));

        return payData;
    }


    private KkoPayNoticeRespData createRespData(TbInputDataXit tbInputDataXit, Map<PayDataKeys, Object> payData) {

        Map<String, Object> detail = new HashMap<>();
        detail.put("item_type", "TEXT");
        detail.put("elements", Arrays.asList(String.format("상세 통행료: %s", payData.get(PayDataKeys.detailPasngChrgeSm))
                , String.format("상세 부가통행료: %s", payData.get(PayDataKeys.detailAdiPasngChrgeSm))));
        List<Map<String, Object>> details = Arrays.asList(detail);

        return KkoPayNoticeRespData.builder()
                .title(tbInputDataXit.getTbInputXit().getFsJob().getJobName())
                .amount((Integer) payData.get(PayDataKeys.amount))
                .expireType("D1")
                .payExpireDate((String) payData.get(PayDataKeys.payExpireDate))
                .details(details)
                .build();
    }

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

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


    @Override
    public PayApiRespDTO<KkoPayNoticeRespData> generateResult(Map<String, Object> reqParam, Map<String, Object> data, EnsException e) {
        if (e == null) {    
            KkoPayNoticeRespData kkoPayNoticeRespData = (KkoPayNoticeRespData) data.get(DataKeys.kkoPayNoticeRespData.name());
            return PayApiRespDTO.<KkoPayNoticeRespData>okBuilder()
                    .data(kkoPayNoticeRespData)
                    .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.<KkoPayNoticeRespData>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<>());
        String billerUserKey = (String) reqData.get("biller_user_key");

        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.kakaoPay)   
                .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);

    }
}
