package cokr.xit.ens.modules.common.ctgy.intgrnbill.kko.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.modules.common.ctgy.intgrnbill.kko.entity.BillKkoPay;
import cokr.xit.ens.modules.common.ctgy.intgrnbill.kko.entity.QBillKkoPay;
import cokr.xit.ens.modules.common.ctgy.intgrnbill.kko.entity.repository.BillKkoPayRepository;
import cokr.xit.ens.modules.common.ctgy.intgrnbill.kko.service.process.model.KkoPayResultReqData;
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 KkoPayResultService implements PayResultService<PayResultData<BillKkoPay>, Map<String, Object>, PayApiRespDTO<Map<String, Object>>> {

    private final JPAQueryFactory query;
    private final PayUseSysApi payUseSysApi;
    private final BillRepository billRepository;
    private final BillKkoPayRepository billKkoPayRepository;
    private final BillHistRepository billHistRepository;

    private Gson gson = new Gson();

    @Override
    public PayResultData<BillKkoPay> getData(Map<String, Object> reqParam) {

        Map<String, Object> data = (Map<String, Object>) reqParam.getOrDefault("data", new HashMap<>());
        String billerNoticeKey = (String) data.get("biller_notice_key");
        Map<String, Object> parameters = (Map<String, Object>) data.getOrDefault("parameters", new HashMap<>());

        BillKkoPay billKkoPay = query.selectFrom(QBillKkoPay.billKkoPay)
                .where(QBillKkoPay.billKkoPay.billerNoticeKey.eq(billerNoticeKey))
                .fetchOne();
        String billUid = billKkoPay == null ? null : billKkoPay.getBillUid();
        if (billUid == null) billUid = "";
        Bill bill = query.selectFrom(QBill.bill)
                .innerJoin(QBill.bill.orgMng, QOrgMng.orgMng).fetchJoin()
                .where(QBill.bill.billUid.eq(billUid))
                .fetchOne();

        return PayResultData.<BillKkoPay>builder()
                .bill(bill)
                .billDetail(billKkoPay)
                .build();
    }

    @Override
    public void validate(Map<String, Object> reqParam, PayResultData<BillKkoPay> data) {
       
        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<BillKkoPay> data) {
        Bill bill = data.getBill();
        OrgMng orgMng = bill.getOrgMng();

       
        final String useSysUrl = orgMng.getKkoBpPayresultApi();

       
        Optional.of(savePaidInfo(data, reqParam))   
                .map(param -> loadReqData(data, useSysUrl, param))    
                .map(param -> createMessage(param))   
                .map(message -> payUseSysApi.payResult(useSysUrl, message, null))   
                .map(response -> loadRespData(data, response))  
                .map(response -> checkResponse(response))//API 응답 검증
        ;
    }

    private Map<String, Object> savePaidInfo(PayResultData<BillKkoPay> data, Map<String, Object> reqParam) {
        Map<String, Object> reqData = (Map<String, Object>) reqParam.getOrDefault("data", new HashMap<>());
        final String paidAt = CmmnUtil.isEmpty(reqData.get("paid_at")) ? data.getBillDetail().getLastPaidAt() : (String) reqData.get("paid_at");
        final Integer payId = CmmnUtil.isEmpty(reqData.get("pay_id")) ? data.getBillDetail().getLastPayId() : (Integer) reqData.get("pay_id");


        
        data.getBill().setPaidAt(true); 
        data.getBill().setPaidType(PaidTypeCd.kakaoPay);    
        data.getBill().setPaidDt(paidAt);   
        billRepository.save(data.getBill());

        
        data.getBillDetail().setLastPaidAt(paidAt);
        data.getBillDetail().setLastPayId(payId);
        data.getBillDetail().setError(FieldError.initBuilder().build());
        billKkoPayRepository.save(data.getBillDetail());


        return reqParam;
    }


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

    private Map<String, Object> createMessage(Map<String, Object> reqParam) {
//        return reqParam;
        Map<String, Object> data = (Map<String, Object>) reqParam.getOrDefault("data", new HashMap<>());
        KkoPayResultReqData kkoPayResultReqData = gson.fromJson(gson.toJson(data), KkoPayResultReqData.class);
        Map<String, Object> result = new HashMap<>();
        result.put("data", kkoPayResultReqData);
        return result;
    }

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

    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);
        }
        
        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<BillKkoPay> 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<BillKkoPay> 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());
                billKkoPayRepository.save(data.getBillDetail());
            }
        }

    }

    private void saveHistByUseSysPayResult(Map<String, Object> reqParam, PayResultData<BillKkoPay> 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.bpKko)  
                .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);

    }
}
