package cokr.xit.ens.modules.common.ctgy.intgrnbill.support.service.repair.kit;

import cokr.xit.ens.core.exception.code.EnsErrCd;
import cokr.xit.ens.core.utils.CmmnUtil;
import cokr.xit.ens.modules.common.ctgy.intgrnbill.support.entity.BillHist;
import cokr.xit.ens.modules.common.ctgy.intgrnbill.support.entity.repository.BillHistRepository;
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.model.PayApiRespDTO;
import cokr.xit.ens.modules.common.ctgy.intgrnbill.support.service.repair.model.PayResultTransferFailData;
import cokr.xit.ens.modules.common.ctgy.intgrnbill.support.service.repair.model.PayResultTransferProcessVO;
import cokr.xit.ens.modules.common.ctgy.intgrnbill.support.service.repair.model.RepairPayRsltFwdFailSearchDTO;
import cokr.xit.ens.modules.common.ctgy.sys.mng.domain.OrgMng;
import cokr.xit.ens.modules.common.domain.support.FieldError;
import com.google.gson.Gson;
import com.querydsl.core.BooleanBuilder;
import com.querydsl.core.types.dsl.CaseBuilder;
import com.querydsl.jpa.impl.JPAQueryFactory;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;

import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

import static cokr.xit.ens.modules.common.ctgy.intgrnbill.support.entity.QBillHist.billHist;
import static cokr.xit.ens.modules.common.ctgy.sys.mng.domain.QOrgMng.orgMng;


public abstract class RepairPayResultTransferFail {

    protected JPAQueryFactory query;
    protected RepairPayRsltFwdFailSearchDTO searchDTO;
    private List<PayResultTransferFailData> transferFailDatas = null;
    private Gson gson = new Gson();


    /**
     * 납부(결제)결과 전달에 실패한 청구서 목록 조회
     *
     * @return
     */
    public List<PayResultTransferFailData> findDatas() {
        
        BooleanBuilder builder = new BooleanBuilder();
        builder.and(billHist.reqSe.in(Arrays.asList(BillReqSeCd.US_PAYRSLT, BillReqSeCd.PAYRSLT)));
        builder.and(billHist.billSe.eq(getBillSeCd()));
        if (!CmmnUtil.isEmpty(searchDTO.getSchOrgCd()))
            builder.and(billHist.orgMng.orgCd.in(searchDTO.getSchOrgCd()));
        if (!CmmnUtil.isEmpty(searchDTO.getSchOrgNm()))
            builder.and(billHist.orgMng.orgNm.in(searchDTO.getSchOrgNm()));
        if (!CmmnUtil.isEmpty(searchDTO.getSchBillUid()))
            builder.and(billHist.billUid.in(searchDTO.getSchBillUid()));


        
        List<PayResultTransferFailData> list = query
                .select(billHist.linkedUuid
                        , billHist.linkedUuid.count().longValue().as("totCnt")          
                        , new CaseBuilder().when(billHist.error.errorCode.isNotNull()
                                        .and((billHist.error.errorMessage.contains("REQUEST_TIMEOUT I/O error on")
                                                .or(billHist.error.errorMessage.contains("404 NOT_FOUND"))
                                                .or(billHist.error.errorCode.eq(PayUseSysResCd.FAILED_SAVE_PAYRSLT.getCode()))
                                        ))
                                )
                                .then(1)
                                .otherwise(0)
                                .sum().longValue().as("failCnt")    
                        , billHist.orgMng.orgCd
                        , billHist.orgMng.orgNm
                        , billHist.billSe
                        , billHist.id.max().as(billHist.id)
                )
                .from(billHist)
                .innerJoin(billHist.orgMng, orgMng)
                .where(builder)
                .groupBy(billHist.orgMng, orgMng.orgNm, billHist.linkedUuid, billHist.billSe)
                .fetch()
                .stream()
                .filter(tuple -> tuple.get(1, Long.class) == tuple.get(2, Long.class)) 
                .map(tuple ->
                        PayResultTransferFailData.builder()
                                .orgCd(tuple.get(billHist.orgMng.orgCd))
                                .orgNm(tuple.get(billHist.orgMng.orgNm))
                                .billSe(tuple.get(billHist.billSe))
                                .billerUserKey(tuple.get(billHist.linkedUuid))
                                .id(tuple.get(billHist.id))
                                .build()
                )
                .collect(Collectors.toList());

        this.transferFailDatas = list;
        return list;
    }

    public List<BillHist> transferData(PayUseSysApi api, BillHistRepository billHistRepository) {
        if (this.transferFailDatas == null)
            this.findDatas();

        List<BillHist> billHistList = this.transferFailDatas.stream()
                .map(data -> PayResultTransferProcessVO
                        .builder()
                        .data(data)
                        .build()
                )
                .map(vo -> {
                    vo.setReqBillHist(query.selectFrom(billHist)
                            .where(billHist.id.eq(vo.getData().getId()))
                            .fetchOne());
                    return vo;
                })
                .filter(vo -> !CmmnUtil.isEmpty(vo.getReqBillHist()))
                .map(vo -> {
                    final String apiUrl = getPayResultApiUrl(vo.getReqBillHist().getOrgMng());
                    ResponseEntity<String> response = api.payResult(apiUrl, gson.fromJson(vo.getReqBillHist().getRequestData(), Map.class), null);
                    vo.setResponse(response);
                    return vo;
                })
                .map(vo -> {
                    ResponseEntity<String> response = vo.getResponse();
                    
                    if (!HttpStatus.OK.equals(response.getStatusCode())) {
                        vo.setError(FieldError.initBuilder()
                                .errorCode(EnsErrCd.API_COMM_ERROR.getCode())
                                .errorMessage(EnsErrCd.API_COMM_ERROR.getCodeNm() + " " + response.getStatusCode().toString())
                                .build());
                        return vo;
                    }
                    
                    PayApiRespDTO<Map<String, Object>> result = null;
                    try {
                        result = gson.fromJson(response.getBody(), PayApiRespDTO.class);
                    } catch (Exception ex) {
                        vo.setError(FieldError.initBuilder()
                                .errorCode(EnsErrCd.INVALID_RESPONSE.getCode())
                                .errorMessage(EnsErrCd.INVALID_RESPONSE.getCodeNm() + " " + ex.getMessage())
                                .build());
                        return vo;
                    }
                    
                    try {
                        PayUseSysResCd.valueOf(result.getResCode());
                    } catch (Exception ex) {
                        vo.setError(FieldError.initBuilder()
                                .errorCode(EnsErrCd.INVALID_RES_CODE.getCode())
                                .errorMessage(EnsErrCd.INVALID_RES_CODE.getCodeNm() + " " + ex.getMessage())
                                .build());
                        return vo;
                    }
                    return vo;
                })
                .map(vo -> BillHist.builder()
                        .billUid(vo.getReqBillHist().getBillUid())
                        .billSe(vo.getReqBillHist().getBillSe())  
                        .reqSe(BillReqSeCd.US_PAYRSLT)  
                        .orgMng(vo.getReqBillHist().getOrgMng())
                        .linkedUuid(vo.getReqBillHist().getLinkedUuid())
                        .requestData(vo.getReqBillHist().getRequestData())
                        .responseData(vo.getResponse().getBody())
                        .error(vo.getError())
                        .build())
                .collect(Collectors.toList());

        billHistRepository.saveAll(billHistList);
        return billHistList;
    }


    abstract protected String getPayResultApiUrl(OrgMng orgMng);

    abstract protected BillSeCd getBillSeCd();

}
