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.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.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.model.PayApiRespDTO;
import cokr.xit.ens.modules.common.ctgy.intgrnbill.support.service.process.kit.PayAbleService;
import cokr.xit.ens.modules.common.ctgy.intgrnbill.support.service.process.model.PayAbleData;
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.reflect.TypeToken;
import com.querydsl.jpa.impl.JPAQueryFactory;
import lombok.Getter;
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.Arrays;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;

@Slf4j
@RequiredArgsConstructor
@Service
public class KkoPayAbleService implements PayAbleService<PayAbleData<BillKkoPay>, Map<String, Object>, PayApiRespDTO<Map<String, Object>>> {
    private final BillKkoPayRepository billKkoPayRepository;

    private final JPAQueryFactory query;
    private final PayUseSysApi payUseSysApi;
    private final BillHistRepository billHistRepository;

    private Gson gson = new Gson();

    private enum KkoErrCd {
        E403(PayUseSysResCd.NO_AMOUNT_PAYABLE),
        E501(PayUseSysResCd.OVERPAYABLE_AMOUNT),
        E502(PayUseSysResCd.NO_TARGET_PAYABLE),
        E506(PayUseSysResCd.EXPIRED_DATE),
        E601(PayUseSysResCd.FAILED_CHECK_INFO),
        ;
        @Getter
        private PayUseSysResCd payUseSysResCd;

        KkoErrCd(PayUseSysResCd payUseSysResCd) {
            this.payUseSysResCd = payUseSysResCd;
        }

        static public KkoErrCd payUseSysResCdOf(String payUseSysResCd) {
            KkoErrCd result = null;
            for (KkoErrCd kkoErrCd : KkoErrCd.values()) {
                if (kkoErrCd.getPayUseSysResCd().getCode().equals(payUseSysResCd)) {
                    result = kkoErrCd;
                    break;
                }
            }
            return result;
        }
    }

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

        Map<String, Object> data = (Map<String, Object>) reqParam.getOrDefault("data", new HashMap<>());
        String billerUserKey = (String) data.get("biller_user_key");
        String billedYearMonth = (String) data.get("billed_year_month");
        String ordinal = (String) data.get("ordinal");
        String billerNoticeKey = (String) data.get("biller_notice_key");
        Map<String, Object> parameters = (Map<String, Object>) data.getOrDefault("parameters", new HashMap<>());
        Integer amount = (Integer) data.get("amount");

        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 PayAbleData.<BillKkoPay>builder()
                .bill(bill)
                .billDetail(billKkoPay)
                .build();
    }

    @Override
    public void validate(Map<String, Object> reqParam, PayAbleData<BillKkoPay> data) {
        Map<String, Object> m = (Map<String, Object>) reqParam.getOrDefault("data", new HashMap<>());
       
        if (CmmnUtil.isEmpty(m.get("biller_user_key")))
            throw new EnsException(EnsErrCd.NO_REQUIRED_PARAM, "빌러유저키는 필수값 입니다.");
        if (CmmnUtil.isEmpty(m.get("biller_notice_key")))
            throw new EnsException(EnsErrCd.NO_REQUIRED_PARAM, "청구서식별키는 필수값 입니다.");
        if (CmmnUtil.isEmpty(m.get("amount")))
            throw new EnsException(EnsErrCd.NO_REQUIRED_PARAM, "납부요청금액 필수값 입니다.");
       
        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, PayAbleData<BillKkoPay> data) {
        Bill bill = data.getBill();
        OrgMng orgMng = bill.getOrgMng();
        BillKkoPay billKkoPay = data.getBillDetail();


       
        final String useSysUrl = orgMng.getKkoBpPrepayApi();

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

    }

    private Map<String, Object> createMessage(Map<String, Object> reqParam) {
//        Map<String, Object> data = (Map<String, Object>) reqParam.getOrDefault("data", new HashMap<>());
//        Map<String, Object> param = new HashMap<>();
//        param.put("biller_user_key", data.get("biller_user_key"));
//        param.put("billed_year_month", data.get("billed_year_month"));
//        param.put("ordinal", data.get("ordinal"));
//        param.put("biller_notice_key", data.get("biller_notice_key"));
//        param.put("parameters", data.get("parameters"));
//        param.put("amount", data.get("amount"));
//        return param;
        return reqParam;
    }

    private Map<String, Object> loadReqData(PayAbleData<BillKkoPay> data, String url, Map<String, Object> param) {
        
        data.getReqAndRespInf().setUrl(url);
        data.getReqAndRespInf().setReqData(gson.toJson(param));
        return param;
    }

    private ResponseEntity<String> loadRespData(PayAbleData<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(), new TypeToken<PayApiRespDTO<Map<String, Object>>>() {
            }.getType());
        } catch (Exception ex) {
            throw new EnsException(EnsErrCd.INVALID_RESPONSE, EnsErrCd.INVALID_RESPONSE.getCodeNm(), ex);
        }
        
        try {
            PayUseSysResCd.valueOf(result.getResCode());
        } catch (Exception ex) {
            PayApiRespDTO<Map<String, Object>> finalResult = result;
            
            if (Arrays.stream(KkoErrCd.values()).noneMatch(kkoErrCd -> kkoErrCd.name().equals(finalResult.getResCode())))
                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, PayAbleData<BillKkoPay> data, EnsException e) {
        if (e == null) {    
            Gson gson = new Gson();
            final String respRawData = data.getReqAndRespInf().getRespRawData();

            PayApiRespDTO<Map<String, Object>> resp = gson.fromJson(respRawData, new TypeToken<PayApiRespDTO<Map<String, Object>>>() {
            }.getType());
            
            String resCode = resp.getResCode();
            String matchResCode = KkoErrCd.payUseSysResCdOf(resCode).name();
            if (matchResCode != null) resCode = matchResCode;

            return PayApiRespDTO.<Map<String, Object>>errDataBuilder()
                    .resCode(resCode)
                    .message(resp.getMessage())
                    .data(resp.getData())
                    .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, PayAbleData<BillKkoPay> data, EnsException e) {
        saveHistByUseSysPayAble(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());
            }
        } else {
            data.getBillDetail().setError(FieldError.initBuilder().build());
            billKkoPayRepository.save(data.getBillDetail());
        }
    }

    public void saveHistByUseSysPayAble(Map<String, Object> reqParam, PayAbleData<BillKkoPay> data, EnsException e) {
        ReqAndRespInf reqAndRespInf = data.getReqAndRespInf();

        BillHist billHist = BillHist.builder()
                .billUid(CmmnUtil.isEmpty(data.getBill()) ? null : data.getBill().getBillUid())
                .billSe(BillSeCd.bpKko)  
                .reqSe(BillReqSeCd.US_PREPAY)  
                .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);
    }

}
