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.KkoPayNoticeRespData;
import cokr.xit.ens.modules.common.ctgy.intgrnbill.support.api.PayUseSysApi;
import cokr.xit.ens.modules.common.ctgy.intgrnbill.support.code.BillLogSeCd;
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.PayNoticeService;
import cokr.xit.ens.modules.common.ctgy.intgrnbill.support.service.process.model.PayNoticeData;
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 KkoPayNoticeService implements PayNoticeService<PayNoticeData<BillKkoPay>, Map<String, Object>, PayApiRespDTO<KkoPayNoticeRespData>> {

    private final JPAQueryFactory query;
    private final PayUseSysApi payUseSysApi;
    private final BillKkoPayRepository billKkoPayRepository;
    private final BillHistRepository billHistRepository;
    private Gson gson = new Gson();


    private enum KkoErrCd {
        E402(PayUseSysResCd.NOT_FOUND_PAYDATA),
        E404(PayUseSysResCd.PAYDATA_FIND_FAILED),
        ;
        @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 PayNoticeData<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 billerNoticeKey = (String) data.get("biller_notice_key");
        String userBirth = (String) data.get("user_birth");
        Map<String, Object> parameters = (Map<String, Object>) data.getOrDefault("parameters", new HashMap<>());
        String orgCd = (String) parameters.get("orgCd");

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

    @Override
    public void validate(Map<String, Object> reqParam, PayNoticeData<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(data.getBill()))
            throw new EnsException(EnsErrCd.NO_DATA_FOUND, "일치하는 청구서 자료가 없습니다.");
        if (CmmnUtil.isEmpty(data.getBillDetail()))
            throw new EnsException(EnsErrCd.NO_DATA_FOUND, "일치하는 청구서 상세 자료가 없습니다.");
        if (!data.getBillDetail().getBillerUserKey().equals(m.get("biller_user_key")))
            throw new EnsException(EnsErrCd.INVALID_DATA, "요청과 실제 빌러유저키가 일치하지 않습니다.");
    }

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

       
        final String useSysUrl = orgMng.getKkoBpNoticeApi();

       
        Optional.of(billKkoPay.getBillerUserKey())
                .map(billerUserKey -> createMessage(reqParam)) 
                .map(param -> loadReqData(data, useSysUrl, param))    
                .map(param -> payUseSysApi.payNotice(useSysUrl, param, null))   
                .map(response -> loadRespData(data, response))  
                .map(response -> checkResponse(response))
                .map(document -> savePayNoticeInfo(data, document))  
        ;

    }

    private Map<String, Object> createMessage(Map<String, Object> reqParam) {
//        Map<String, Object> m = (Map<String, Object>) reqParam.getOrDefault("data", new HashMap<>());
//        Map<String, Object> mParam = new HashMap<>();
//        mParam.put("billerUserKey", m.get("biller_user_key"));
//        mParam.put("userBirth", m.get("user_birth"));
//        return mParam;
        return reqParam;
    }

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

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


    private PayApiRespDTO<KkoPayNoticeRespData> checkResponse(ResponseEntity<String> response) {
        
        if (!HttpStatus.OK.equals(response.getStatusCode()))
            throw new EnsException(EnsErrCd.API_COMM_ERROR, "["+ BillLogSeCd.US +"] " + EnsErrCd.API_COMM_ERROR.getCodeNm() + " " + response.getStatusCode().toString(), response.getBody());
        
        PayApiRespDTO<KkoPayNoticeRespData> result = null;
        try {
            result = gson.fromJson(response.getBody(), new TypeToken<PayApiRespDTO<KkoPayNoticeRespData>>() {
            }.getType());
        } catch (Exception ex) {
            throw new EnsException(EnsErrCd.INVALID_RESPONSE, "["+ BillLogSeCd.US +"] " + EnsErrCd.INVALID_RESPONSE.getCodeNm(), ex);
        }
        
        try {
            PayUseSysResCd.valueOf(result.getResCode());
        } catch (Exception ex) {
            PayApiRespDTO<KkoPayNoticeRespData> finalResult = result;
            
            if (Arrays.stream(KkoErrCd.values()).noneMatch(kkoErrCd -> kkoErrCd.name().equals(finalResult.getResCode())))
                throw new EnsException(EnsErrCd.INVALID_RES_CODE, "["+ BillLogSeCd.US +"] " + EnsErrCd.INVALID_RES_CODE.getCodeNm(), ex);
        }
        if (!PayUseSysResCd.OK.getCode().equals(result.getResCode())) {
            result.setMessage("["+ BillLogSeCd.US +"] " + result.getMessage());
            throw new EnsException(EnsErrCd.RESPONSED_FAILURE_CODE, EnsErrCd.RESPONSED_FAILURE_CODE.getCodeNm(), result);
        }

        return result;
    }

    private PayApiRespDTO<KkoPayNoticeRespData> savePayNoticeInfo(PayNoticeData<BillKkoPay> data, PayApiRespDTO<KkoPayNoticeRespData> document) {
        KkoPayNoticeRespData noticeData = document.getData();

        data.getBillDetail().setTitle(noticeData.getTitle());
        data.getBillDetail().setAmount(noticeData.getAmount());
        data.getBillDetail().setTaxFreeAmount(noticeData.getTaxFreeAmount());
        data.getBillDetail().setVatAmount(noticeData.getVatAmount());
        data.getBillDetail().setExpireType(noticeData.getExpireType());
        data.getBillDetail().setPayExpireDate(noticeData.getPayExpireDate());
        data.getBillDetail().setSecondPayExpireDate(noticeData.getSecondPayExpireDate());
        data.getBillDetail().setBankAccounts(CmmnUtil.isEmpty(noticeData.getBankAccounts()) ? null : gson.toJson(noticeData.getBankAccounts()));
        data.getBillDetail().setDetails(CmmnUtil.isEmpty(noticeData.getDetails()) ? null : gson.toJson(noticeData.getDetails()));
        data.getBillDetail().setError(FieldError.initBuilder().build());

        billKkoPayRepository.save(data.getBillDetail());

        return document;
    }

    @Override
    public PayApiRespDTO<KkoPayNoticeRespData> generateResult(Map<String, Object> reqParam, PayNoticeData<BillKkoPay> data, EnsException e) {
        if (e == null) {    
            Gson gson = new Gson();
            final String respRawData = data.getReqAndRespInf().getRespRawData();

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

            return PayApiRespDTO.<KkoPayNoticeRespData>errDataBuilder()
                    .resCode(resCode)
                    .message(resp.getMessage())
                    .data(resp.getData())
                    .build();
        } else {   
            if (EnsErrCd.RESPONSED_FAILURE_CODE.equals(e.getErrCd())) {
                PayApiRespDTO respDTO = (PayApiRespDTO) e.getData();
                
                String resCode = respDTO.getResCode();
                String matchResCode = KkoErrCd.payUseSysResCdOf(resCode).name();
                if (matchResCode != null) resCode = matchResCode;
                return PayApiRespDTO.<KkoPayNoticeRespData>errDataBuilder()
                        .resCode(resCode)
                        .message(respDTO.getMessage())
                        .data(null)
                        .build();
            } else {
                return PayApiRespDTO.<KkoPayNoticeRespData>errDataBuilder()
                        .resCode(e.getErrCd().getCode())
                        .message(e.getMessage())
                        .data(null)
                        .build();
            }
        }
    }

    @Override
    public void saveHist(Map<String, Object> reqParam, PayNoticeData<BillKkoPay> data, EnsException e) {
        saveHistByUseSysNotice(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 saveHistByUseSysNotice(Map<String, Object> reqParam, PayNoticeData<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_NOTICE)  
                .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);
    }
}
