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

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.ctgy.intgrnbill.nv.api.BillNvEasyPayApiSpec;
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.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.PayCancelService;
import cokr.xit.ens.modules.common.ctgy.intgrnbill.support.service.process.model.PayCancelData;
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.google.gson.GsonBuilder;
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 org.springframework.util.LinkedMultiValueMap;

import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.atomic.AtomicReference;

@Slf4j
@RequiredArgsConstructor
@Service
public class NvPayCancelService implements PayCancelService<PayCancelData<BillNvEasyPay>, Map<String, Object>, PayApiRespDTO<Map<String, Object>>> {

    private final JPAQueryFactory query;
    private final BillNvEasyPayApiSpec billNvEasyPayApi;
    private final BillRepository billRepository;
    private final BillNvEasyPayRepository billNvEasyPayRepository;
    private final BillHistRepository billHistRepository;
    private final PayUseSysApi payUseSysApi;

    private Gson gson = new GsonBuilder().registerTypeAdapter(Map.class, new MapDeserailizer()).create();

    private enum DataKeys {
        useSysPayCancelReqMessage,
        useSysPayCancelRespMessage,
    }

    @Override
    public PayCancelData<BillNvEasyPay> getData(Map<String, Object> reqParam) {
        String billUid = (String) reqParam.getOrDefault("billUid", "");

        Bill bill = query.selectFrom(QBill.bill)
                .innerJoin(QBill.bill.orgMng, QOrgMng.orgMng).fetchJoin()
                .where(QBill.bill.billUid.eq(billUid))
                .fetchOne();
        BillNvEasyPay billNvEasyPay = query.selectFrom(QBillNvEasyPay.billNvEasyPay)
                .where(QBillNvEasyPay.billNvEasyPay.billUid.eq(billUid))
                .fetchOne();

        return PayCancelData.<BillNvEasyPay>builder()
                .bill(bill)
                .billDetail(billNvEasyPay)
                .build();
    }

    @Override
    public void validate(Map<String, Object> reqParam, PayCancelData<BillNvEasyPay> data) {
       
        if (CmmnUtil.isEmpty(reqParam.get("billUid")))
            throw new EnsException(EnsErrCd.NO_REQUIRED_PARAM, "청구서UID는 필수값 입니다.");
        if (CmmnUtil.isEmpty(reqParam.get("cancelReason")))
            throw new EnsException(EnsErrCd.NO_REQUIRED_PARAM, "취소사유는 필수값 입니다.");
        if (String.valueOf(reqParam.get("cancelReason")).length() > 80)
            throw new EnsException(EnsErrCd.INVALID_REQUEST, "취소사유는 최대 80자까지 가능 합니다.");
        if (CmmnUtil.isEmpty(reqParam.get("cancelRequester")))
            throw new EnsException(EnsErrCd.NO_REQUIRED_PARAM, "취소요청자는 필수값 입니다.");
        if (!("1".equals(reqParam.get("cancelRequester")) || "2".equals(reqParam.get("cancelRequester"))))
            throw new EnsException(EnsErrCd.INVALID_REQUEST, "유효하지 않은 취소요청자 구분값 입니다."); //1:구매자, 2:가맹점 관리자 - 애매할 경우 가맹점관리자로 설정
       
        if (CmmnUtil.isEmpty(data.getBill()))
            throw new EnsException(EnsErrCd.NO_DATA_FOUND, "일치하는 청구서 자료가 없습니다.");
        if (CmmnUtil.isEmpty(data.getBillDetail()))
            throw new EnsException(EnsErrCd.NO_DATA_FOUND, "일치하는 청구서 상세 자료가 없습니다.");
        if (CmmnUtil.isEmpty(data.getBillDetail().getLastPaymentId()))
            throw new EnsException(EnsErrCd.INVALID_DATA, "미결제(또는 결제취소) 자료 입니다.");
        final Integer sumAmount = data.getBillDetail().getTaxScopeAmount() + data.getBillDetail().getTaxExScopeAmount() + (data.getBillDetail().getEnvironmentDepositAmount() == null ? 0 : data.getBillDetail().getEnvironmentDepositAmount());
        if (!data.getBillDetail().getTotalPayAmount().equals(sumAmount))
            throw new EnsException(EnsErrCd.INVALID_DATA, "결제금액과 세부(과세/면세/컵보증금)금액 합계 불일치.");
    }

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

       
        final String partnerId = orgMng.getNvBpPartnerId();
        final String clientId = orgMng.getNvBpClientId();
        final String clientSecret = orgMng.getNvBpClientSecret();

        final String useSysUrl = orgMng.getNvBpPaycancelApi();
        AtomicReference<Map<String, Object>> fwdData = new AtomicReference<>();


       
        Optional.of(createMessage(reqParam, billNvEasyPay)) 
                .map(param -> loadReqData(data, param))    
                .map(param -> billNvEasyPayApi.payCancel(partnerId, clientId, clientSecret, param))  
                .map(response -> loadRespData(data, response))  
                .map(response -> checkResponse(response))
                .map(respData -> savePayCancelInfo(data, respData))  
                .filter(respData -> useSysUrl != null)
                .map(respData -> {
                    fwdData.set(respData);
                    return respData;
                })
                .map(respData -> loadFwdUseSysPayCancelReqData(data, respData))            
                .map(respData -> payUseSysApi.payCancelResult(useSysUrl, respData, null))  
                .map(response -> loadFwdUseSysPayCancelRespData(data, response))           
        ;

    }

    protected LinkedMultiValueMap createMessage(Map<String, Object> reqParam, BillNvEasyPay billNvEasyPay) {
        LinkedMultiValueMap reqData = new LinkedMultiValueMap<>();
        reqData.add("paymentId", billNvEasyPay.getLastPaymentId());
        reqData.add("merchantUserKey", billNvEasyPay.getBillerUserKey());
        reqData.add("cancelReason", reqParam.get("cancelReason"));
        reqData.add("cancelRequester", reqParam.get("cancelRequester"));
        reqData.add("cancelAmount", billNvEasyPay.getTotalPayAmount());
        reqData.add("taxScopeAmount", billNvEasyPay.getTaxScopeAmount());
        reqData.add("taxExScopeAmount", billNvEasyPay.getTaxExScopeAmount());
        if (!CmmnUtil.isEmpty(billNvEasyPay.getEnvironmentDepositAmount()))
            reqData.add("environmentDepositAmount", billNvEasyPay.getEnvironmentDepositAmount());
        reqData.add("doCompareRest", 0); 
        reqData.add("expectedRestAmount", null); 
        return reqData;
    }

    protected LinkedMultiValueMap loadReqData(PayCancelData<BillNvEasyPay> data, LinkedMultiValueMap param) {
        
        data.getReqAndRespInf().setUrl("");
        data.getReqAndRespInf().setReqData(CmmnUtil.toJsonString(param));

        return param;
    }

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

    protected 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());
        
        Map<String, Object> result = null;
        try {
            result = gson.fromJson(response.getBody(), Map.class);
        } catch (Exception ex) {
            throw new EnsException(EnsErrCd.INVALID_RESPONSE, EnsErrCd.INVALID_RESPONSE.getCodeNm(), ex);
        }
        
        //※응답코드 중 CancelNotComplete는 네이버페이 취소 진행 중 상태로 빠른 시일 내에 자동 취소될 예정이므로 가맹점에서는 취소 성공으로 처리 필요
        if (!("Success".equals(result.get("code")) || "CancelNotComplete".equals(result.get("code"))))
//            throw new EnsException(EnsErrCd.RESPONSED_FAILURE_CODE, String.format("[%s] %s", result.get("code"), result.get("message")));
            //2023.03.09 네이버페이 검수 결과 - 결제창 실패응답 화면에 resultMessage 원문만 노출 요함
            throw new EnsException(EnsErrCd.RESPONSED_FAILURE_CODE, (String) result.get("message"));

        return result;
    }

    protected Map<String, Object> savePayCancelInfo(PayCancelData<BillNvEasyPay> data, Map<String, Object> respData) {

        Map<String, Object> body = (Map<String, Object>) respData.getOrDefault("body", new HashMap<>());
        final String cancelYmdt = (String) body.get("cancelYmdt");

        
        if (PaidTypeCd.naverPay.equals(data.getBill().getPaidType())) {
            data.getBill().setPaidAt(false); 
            data.getBill().setPaidType(PaidTypeCd.naverPay);    
            data.getBill().setPaidCancelDt(cancelYmdt);   
            billRepository.save(data.getBill());
        }

        
        data.getBillDetail().setLastPaymentId(null);    
        data.getBillDetail().setLastPayHistId((String) body.get("payHistId"));  
        data.getBillDetail().setLastCancelPaymentId((String) body.get("paymentId"));    
        data.getBillDetail().setError(FieldError.initBuilder().build());
        billNvEasyPayRepository.save(data.getBillDetail());

        return respData;
    }

    protected Map<String, Object> loadFwdUseSysPayCancelReqData(PayCancelData<BillNvEasyPay> data, Map<String, Object> respData) {
        data.getReqAndRespInf().getEtc().put(DataKeys.useSysPayCancelReqMessage.name(), gson.toJson(respData));
        return respData;
    }

    protected ResponseEntity<String> loadFwdUseSysPayCancelRespData(PayCancelData<BillNvEasyPay> data, ResponseEntity<String> response) {
        data.getReqAndRespInf().getEtc().put(DataKeys.useSysPayCancelRespMessage.name(), response.getBody());
        return response;
    }


    @Override
    public PayApiRespDTO<Map<String, Object>> generateResult(Map<String, Object> reqParam, PayCancelData<BillNvEasyPay> data, EnsException e) {
        if (e == null) {    
            Gson gson = new Gson();
            final String respRawData = data.getReqAndRespInf().getRespRawData();
            return PayApiRespDTO.<Map<String, Object>>errDataBuilder()
                    .resCode(PayUseSysResCd.OK.getCode())
                    .message(PayUseSysResCd.OK.getCodeNm())
                    .data(gson.fromJson(respRawData, Map.class))
                    .build();
        } 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, PayCancelData<BillNvEasyPay> data, EnsException e) {
        saveHistByPayCancel(reqParam, data, e); 
        saveHistByFwdUseSysPayCancel(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 saveHistByPayCancel(Map<String, Object> reqParam, PayCancelData<BillNvEasyPay> data, EnsException e) {
        ReqAndRespInf reqAndRespInf = data.getReqAndRespInf();

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

        billHistRepository.save(billHist);
    }

    protected void saveHistByFwdUseSysPayCancel(PayCancelData<BillNvEasyPay> data, EnsException e) {
        ReqAndRespInf reqAndRespInf = data.getReqAndRespInf();
        final String requestData = (String) reqAndRespInf.getEtc().get(DataKeys.useSysPayCancelReqMessage.name());
        final String responseData = (String) reqAndRespInf.getEtc().get(DataKeys.useSysPayCancelRespMessage.name());

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

            billHistRepository.save(billHist);
        }
    }
}
