package cokr.xit.ens.modules.common.ctgy.intgrnbill.nv.service.process;

import cokr.xit.ens.biz.iup.domain.TbNaverPayResult;
import cokr.xit.ens.biz.iup.domain.repository.TbNaverPayResultRepository;
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.entity.BillNvEasyPay;
import cokr.xit.ens.modules.common.ctgy.intgrnbill.nv.entity.QBillNvEasyPay;
import cokr.xit.ens.modules.common.ctgy.intgrnbill.nv.entity.repository.BillNvEasyPayRepository;
import cokr.xit.ens.modules.common.ctgy.intgrnbill.nv.service.process.model.NvPayResultDetail;
import cokr.xit.ens.modules.common.ctgy.intgrnbill.nv.service.process.model.NvPayResultReqData;
import cokr.xit.ens.modules.common.ctgy.intgrnbill.support.api.PayUseSysApi;
import cokr.xit.ens.modules.common.ctgy.intgrnbill.support.code.BillReqSeCd;
import cokr.xit.ens.modules.common.ctgy.intgrnbill.support.code.BillSeCd;
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.entity.Bill;
import cokr.xit.ens.modules.common.ctgy.intgrnbill.support.entity.BillHist;
import cokr.xit.ens.modules.common.ctgy.intgrnbill.support.entity.QBill;
import cokr.xit.ens.modules.common.ctgy.intgrnbill.support.entity.repository.BillHistRepository;
import cokr.xit.ens.modules.common.ctgy.intgrnbill.support.entity.repository.BillRepository;
import cokr.xit.ens.modules.common.ctgy.intgrnbill.support.model.PayApiRespDTO;
import cokr.xit.ens.modules.common.ctgy.intgrnbill.support.service.process.kit.PayResultService;
import cokr.xit.ens.modules.common.ctgy.intgrnbill.support.service.process.model.PayResultData;
import cokr.xit.ens.modules.common.ctgy.intgrnbill.support.service.process.model.ReqAndRespInf;
import cokr.xit.ens.modules.common.ctgy.sys.mng.domain.OrgMng;
import cokr.xit.ens.modules.common.ctgy.sys.mng.domain.QOrgMng;
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.util.HashMap;
import java.util.Map;
import java.util.Optional;

@Slf4j
@RequiredArgsConstructor
@Service
public class NvPayResultService implements PayResultService<PayResultData<BillNvEasyPay>, Map<String, Object>, PayApiRespDTO<Map<String, Object>>> {
    private final BillNvEasyPayRepository billNvEasyPayRepository;

    private final JPAQueryFactory query;
    private final PayUseSysApi payUseSysApi;
    private final BillRepository billRepository;
    private final BillHistRepository billHistRepository;
    private final TbNaverPayResultRepository tbNaverPayResultRepository;

    private Gson gson = new Gson();

    @Override
    public PayResultData<BillNvEasyPay> getData(Map<String, Object> reqParam) {
        Map<String, Object> body = (Map<String, Object>) reqParam.getOrDefault("body", new HashMap<>());
        Map<String, Object> detail = (Map<String, Object>) body.getOrDefault("detail", new HashMap<>());
//        final String paymentId = (String) body.getOrDefault("paymentId", "");
//        final String billerUserKey = (String) detail.getOrDefault("merchantUserKey", "");
        final String billUid = (String) detail.getOrDefault("merchantPayKey", "");

        BillNvEasyPay billNvEasyPay = query.selectFrom(QBillNvEasyPay.billNvEasyPay)
//                .where(QBillNvEasyPay.billNvEasyPay.lastPaymentId.eq(paymentId)
//                        .and(QBillNvEasyPay.billNvEasyPay.billerUserKey.eq(billerUserKey))
//                )
                .where(QBillNvEasyPay.billNvEasyPay.billUid.eq(billUid))
                .fetchOne();
        Bill bill = null;
        if (billNvEasyPay != null)
            bill = query.selectFrom(QBill.bill)
                    .innerJoin(QBill.bill.orgMng, QOrgMng.orgMng).fetchJoin()
                    .where(QBill.bill.billUid.eq(billNvEasyPay.getBillUid()))
                    .fetchOne();
        return PayResultData.<BillNvEasyPay>builder()
                .bill(bill)
                .billDetail(billNvEasyPay)
                .build();
    }

    @Override
    public void validate(Map<String, Object> reqParam, PayResultData<BillNvEasyPay> data) {
       
        if (!"Success".equals(reqParam.get("code")))
            throw new EnsException(EnsErrCd.INVALID_REQUEST, String.format("[%s] %s", reqParam.get("code"), reqParam.get("message")));
       
        if (CmmnUtil.isEmpty(data.getBill()))
            throw new EnsException(EnsErrCd.NO_DATA_FOUND, "일치하는 청구서 자료가 없습니다.");
        if (CmmnUtil.isEmpty(data.getBillDetail()))
            throw new EnsException(EnsErrCd.NO_DATA_FOUND, "일치하는 청구서 상세 자료가 없습니다.");
    }

    @Override
    public void callApi(Map<String, Object> reqParam, PayResultData<BillNvEasyPay> data) {
        Bill bill = data.getBill();
        OrgMng orgMng = bill.getOrgMng();

       
        final String useSysUrl = orgMng.getNvBpPayresultApi();

        log.info("********** NV savePaidInfo reqParam : " + reqParam);

        Optional.ofNullable(gson.fromJson(gson.toJson(reqParam.get("body")), NvPayResultReqData.class))
                .map(nvPayResultReqData -> toEntity(nvPayResultReqData))
                .map(entity -> savePayResult(entity));


        Optional.of(savePaidInfo(data, reqParam))                   
                .map(param -> loadReqData(data, useSysUrl, param))  
                .map(param -> createMessage(param))                 
                .map(param -> payUseSysApi.payResult(useSysUrl, param, null))   
                .map(response -> loadRespData(data, response))      
                .map(response -> checkResponse(response))          
        ;
    }

    protected Map<String, Object> savePaidInfo(PayResultData<BillNvEasyPay> data, Map<String, Object> reqParam) {
        Map<String, Object> body = (Map<String, Object>) reqParam.getOrDefault("body", new HashMap<>());
        Map<String, Object> detail = (Map<String, Object>) body.getOrDefault("detail", new HashMap<>());
        final String admissionYmdt = (String) detail.get("admissionYmdt");
        final String paymentId = (String) body.getOrDefault("paymentId", "");


        
        data.getBill().setPaidAt(true);
        data.getBill().setPaidType(PaidTypeCd.naverPay);    
        data.getBill().setPaidDt(admissionYmdt);   
        billRepository.save(data.getBill());

        
        data.getBillDetail().setLastPaymentId(paymentId);
        data.getBillDetail().setError(FieldError.initBuilder().build());
        billNvEasyPayRepository.save(data.getBillDetail());

        return reqParam;
    }


    protected Map<String, Object> loadReqData(PayResultData<BillNvEasyPay> data, String url, Map<String, Object> param) {
        
        data.getReqAndRespInf().setUrl(url);
        data.getReqAndRespInf().setReqData(CmmnUtil.toJsonString(param));
        return param;
    }

    protected Map<String, Object> createMessage(Map<String, Object> reqParam) {
        return reqParam;
    }


    protected ResponseEntity<String> loadRespData(PayResultData<BillNvEasyPay> data, ResponseEntity<String> response) {
        
        data.getReqAndRespInf().setRespRawData(response.getBody());
        return response;
    }

    protected 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);
        }
        
        try {
            PayUseSysResCd.valueOf(result.getResCode());
        } catch (Exception ex) {
            throw new EnsException(EnsErrCd.INVALID_RES_CODE, EnsErrCd.INVALID_RES_CODE.getCodeNm(), ex);
        }

        return result;
    }


    @Override
    public PayApiRespDTO<Map<String, Object>> generateResult(Map<String, Object> reqParam, PayResultData<BillNvEasyPay> data, EnsException e) {
        if (e == null) {    
            final String respRawData = data.getReqAndRespInf().getRespRawData();
            return gson.fromJson(respRawData, PayApiRespDTO.class);
        } else {   
            return PayApiRespDTO.<Map<String, Object>>errDataBuilder()
                    .resCode(e.getErrCd().getCode())
                    .message(e.getMessage())
                    .data(null)
                    .build();
        }
    }

    @Override
    public void saveHist(Map<String, Object> reqParam, PayResultData<BillNvEasyPay> data, EnsException e) {
        saveHistByUseSysPayResult(reqParam, data, e); 

        if (e != null) {    
            if (data.getBillDetail() != null) {
                data.getBillDetail().setError(FieldError.initBuilder()
                        .errorCode(e.getErrCd().getCode())
                        .errorMessage(e.getMessage())
                        .build());
                billNvEasyPayRepository.save(data.getBillDetail());
            }
        }

    }

    protected void saveHistByUseSysPayResult(Map<String, Object> reqParam, PayResultData<BillNvEasyPay> data, EnsException e) {
        ReqAndRespInf reqAndRespInf = data.getReqAndRespInf();

        String requestData = reqAndRespInf.getReqData();
        if (e != null) {
            switch (e.getErrCd()) {
                case NO_DATA_FOUND:
                case INVALID_REQUEST:
                    requestData = gson.toJson(reqParam);
                    break;
            }
        }


        BillHist billHist = BillHist.builder()
                .billUid(CmmnUtil.isEmpty(data.getBill()) ? null : data.getBill().getBillUid())
                .billSe(BillSeCd.bpNv)  
                .reqSe(BillReqSeCd.US_PAYRSLT)  
                .orgMng(CmmnUtil.isEmpty(data.getBill()) ? null : data.getBill().getOrgMng())
                .linkedUuid(CmmnUtil.isEmpty(data.getBillDetail()) ? null : data.getBillDetail().getBillerUserKey())
                .requestData(requestData)
                .responseData(reqAndRespInf.getRespRawData())
                .error(e == null
                        ? FieldError.initBuilder().build()
                        : FieldError.initBuilder()
                        .errorCode(e.getErrCd().getCode())
                        .errorMessage(e.getMessage())
                        .build())
                .build();

        billHistRepository.save(billHist);
    }

    private TbNaverPayResult toEntity(NvPayResultReqData nvPayResultReqData) {
        NvPayResultDetail detail = nvPayResultReqData.getDetail();
        final String billerUserKey = detail.getMerchantUserKey();

        final String jobSe = "EL".equals(billerUserKey.substring(0, 2)) ? "2" : "1";

        return TbNaverPayResult.builder()
                .dataId(billerUserKey)
                .jobSe(jobSe)
                .paymentId(detail.getPaymentId())
                .payHistId(detail.getPayHistId())
                .merchantId(detail.getMerchantId())
                .merchantName(detail.getMerchantName())
                .merchantPayKey(detail.getMerchantPayKey())
                .merchantUserKey(detail.getMerchantUserKey())
                .admissionTypeCode(detail.getAdmissionTypeCode())
                .admissionYmdt(detail.getAdmissionYmdt())
                .tradeConfirmYmdt(detail.getTradeConfirmYmdt())
                .admissionState(detail.getAdmissionState())
                .totalPayAmount(detail.getTotalPayAmount())
                .primaryPayAmount(detail.getPrimaryPayAmount())
                .npointPayAmount(detail.getNpointPayAmount())
                .giftCardAmount(detail.getGiftCardAmount())
                .taxScopeAmount(detail.getTaxScopeAmount())
                .taxExScopeAmount(detail.getTaxExScopeAmount())
                .environmentDepositAmount(detail.getEnvironmentDepositAmount())
                .primaryPayMeans(detail.getPrimaryPayMeans())
                .cardCorpCode(detail.getCardCorpCode())
                .cardNo(detail.getCardNo())
                .cardAuthNo(detail.getCardAuthNo())
                .cardInstCount(detail.getCardInstCount())
                .usedCardPoint(detail.getUsedCardPoint())
                .bankCorpCode(detail.getBankCorpCode())
                .bankAccountNo(detail.getBankAccountNo())
                .productName(detail.getProductName())
                .settleExpected(detail.getSettleExpected())
                .settleExpectAmount(detail.getSettleExpectAmount())
                .payCommissionAmount(detail.getPayCommissionAmount())
                .extraDeduction(detail.getExtraDeduction())
                .useCfmYmdt(detail.getUseCfmYmdt())
                .merchantExtraParameter(detail.getMerchantExtraParameter())
                .userIdentifier(detail.getUserIdentifier())
                .payResultProcessSttus("0")
//                    .creatDt()
                .crtr("ENS_SYS")
//                    .updtDt()
//                    .updusr()
                .build();

    }

    private TbNaverPayResult savePayResult(TbNaverPayResult entity) {

        tbNaverPayResultRepository.save(entity);
        return entity;
    }
}
