package com.cusc.nirvana.user.rnr.fp.service.impl;

import com.alibaba.fastjson.JSON;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.cache.CacheFactory;
import com.cache.exception.CacheException;
import com.cusc.nirvana.common.result.Response;
import com.cusc.nirvana.user.rnr.fp.common.MbCodeEnum;
import com.cusc.nirvana.user.rnr.fp.common.config.EncryptionConfig;
import com.cusc.nirvana.user.rnr.fp.constants.RedisConstant;
import com.cusc.nirvana.user.rnr.fp.constants.UploadStatus;
import com.cusc.nirvana.user.rnr.fp.constants.UploadType;
import com.cusc.nirvana.user.rnr.fp.converter.InstructionConverter;
import com.cusc.nirvana.user.rnr.fp.dao.InstructionDao;
import com.cusc.nirvana.user.rnr.fp.dao.entity.InstructionPO;
import com.cusc.nirvana.user.rnr.fp.dto.*;
import com.cusc.nirvana.user.rnr.fp.service.*;
import com.cusc.nirvana.user.rnr.fp.util.CuscFileUtils;
import com.cusc.nirvana.user.rnr.fp.util.Md5CaculateUtil;
import com.cusc.nirvana.user.rnr.fp.util.RnrDataAesUtil;
import com.cusc.nirvana.user.rnr.fp.util.ZipUtils;
import com.cusc.nirvana.user.rnr.mg.client.MgRnrCardInfoClient;
import com.cusc.nirvana.user.rnr.mg.client.MgRnrFileClient;
import com.cusc.nirvana.user.rnr.mg.client.OrgSimRelClient;
import com.cusc.nirvana.user.rnr.mg.constants.RnrFileType;
import com.cusc.nirvana.user.rnr.mg.constants.RnrStatus;
import com.cusc.nirvana.user.rnr.mg.dto.IccIdListRequestDTO;
import com.cusc.nirvana.user.rnr.mg.dto.MgRnrCardInfoDTO;
import com.cusc.nirvana.user.rnr.mg.dto.MgRnrFileDTO;
import com.cusc.nirvana.user.rnr.mg.dto.OrgSimRelDTO;
import com.cusc.nirvana.user.util.CuscStringUtils;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.io.FileUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.context.config.annotation.RefreshScope;
import org.springframework.core.io.FileSystemResource;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.scheduling.annotation.Async;
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.web.client.RestTemplate;

import javax.annotation.Resource;
import java.io.*;
import java.util.Iterator;
import java.util.List;

/**
 * 执行上传实现类
 */
@Slf4j
@Service
@EnableScheduling
@RefreshScope
public class InstructionServiceImpl extends ServiceImpl<InstructionDao, InstructionPO> implements IInstructionService {

    @Value("${T1.instruction.retry.maxAttempts:10}")
    private int maxAttempts;

    @Value("${T1.instruction.retry.lock.expireTime:3600}")
    private int expireTime;

    @Value("${t1.vehicle.t1InstructionKey:}")
    //此接口上传的车辆信息加密密钥由工信部下发
    private String t1InstructionKey;

    @Resource
    private IInstructionLogService iInstructionLogService;

    @Resource
    private CacheFactory cacheFactory;

    @Autowired
    private MgRnrCardInfoClient rnrCardInfoClient;

    @Autowired
    @Qualifier("noBalanceRestTemplateRnrFp")
    RestTemplate noBalanceRestTemplateRnrFp;

    @Autowired
    private IFileService fileService;



    @Autowired
    private MgRnrFileClient rnrFileClient;

//    @Autowired
//    private IFpT1VehicleCompanyService t1VehicleCompanyService;

    @Autowired
    private ChinaMobileT1UploadServiceImpl chinaMobileT1UploadService;

    @Autowired
    private ChinaUnicomT1UploadServiceImpl chinaUnicomT1UploadService;

    @Autowired
    private ChinaTelecomT1UploadServiceImpl chinaTelecomT1UploadServiceImpl;

    @Autowired
    private OrgSimRelClient orgSimRelClient;

    @Autowired
    private EncryptionConfig encryptionConfig;

    @Autowired
    private CtccT1UploadService ctccSftpUtil;

    @Autowired
    private CmccT1UploadService cmccSftpUtil;

    @Autowired
    private OperatorService operatorService;

    @Autowired
    private IT1UploadService it1UploadService;;
    /**
     * 定时任务，从数据库中获取未完成上报的指令，进行上报
     */
    @Scheduled(fixedRateString = "${T1.instruction.retry.interval:300000}")
    public void uploadTask() {
        findFailedJobAndUpload();
    }

    /**
     * 接受上报指令，进行上报
     *
     * @param instructionDTO 上报信息
     * @return 上报结果
     */
    @Override
    public InstructionRespDTO uploadInfo(InstructionDTO instructionDTO) {
        String requestId = instructionDTO.getRequestID();
        String iccid = instructionDTO.getICCID();

        if (StringUtils.isBlank(requestId)) {
            return InstructionRespDTO.createError(requestId, "指令标识缺失");
        }

        if (StringUtils.isBlank(iccid)) {
            return InstructionRespDTO.createError(requestId, "ICCID缺失");
        }

        //直接保存到指令库
        InstructionPO infoPO = insertInstruction(instructionDTO);

        //异步方式上报一次
        asyncUploadInfo(infoPO);

        //返回成功状态，表示接收到了指令并插入指令库
        return InstructionRespDTO.createSuccess(requestId);
    }

    @Override
    public InstructionRespDTO instructionsIssue(InstructionDTO instructionDTO) {
        String requestId = instructionDTO.getRequestID();
        String iccid = instructionDTO.getICCID();

        if (StringUtils.isBlank(requestId)) {
            return InstructionRespDTO.createError(requestId, "指令标识缺失");
        }

        if (StringUtils.isBlank(iccid)) {
            return InstructionRespDTO.createError(requestId, "ICCID缺失");
        }

        //直接保存到指令库
        InstructionPO infoPO = insertInstruction(instructionDTO);

        //异步方式上报一次
        asyncUploadInfoIssue(infoPO);

        //返回成功状态，表示接收到了指令并插入指令库
        return InstructionRespDTO.createSuccess(requestId);
    }

    /**
     * 异步方式上报指令信息
     *
     * @param instructionPO 指令信息
     */
    @Override
    @Async("uploadExecutor")
    public void asyncUploadInfo(InstructionPO instructionPO) {
        tryUploadInfo(instructionPO.getUuid());
    }

    @Async("uploadIssueExecutor")
    public void asyncUploadInfoIssue(InstructionPO instructionPO) {
        tryUploadInfoIssue(instructionPO.getUuid());
    }

    @Override
    public InstructionPO getByUuid(String uuid) {
        QueryWrapper<InstructionPO> queryWrapper = new QueryWrapper<>();
        queryWrapper.eq("uuid", uuid);
        return this.getOne(queryWrapper);
    }

    /**
     * 发送超过重试次数告警信息
     *
     * @param instructionPO 指令信息
     */
    @Override
    public void sendWarningMessage(InstructionPO instructionPO) {
        log.warn("Send warning info, iccid:" + instructionPO.getIccid());
        //发送告警信息
    }

    /**
     * 插入指令信息
     *
     * @param instructionDTO 指令信息
     */
    @Override
    public InstructionPO insertInstruction(InstructionDTO instructionDTO) {
        InstructionPO instructionPO = InstructionConverter.INSTANCE.dtoToPo(instructionDTO);
        instructionPO.setUuid(CuscStringUtils.generateUuid());
        this.save(instructionPO);
        return instructionPO;
    }

    /**
     * 重试上报 指令通道只需要上报文件：入网合同
     *
     * @param uuid 指令业务主键
     */
    @Override
    public void tryUploadInfo(String uuid) {
        String lockKey = RedisConstant.RNR_INSTRUCTION_UPLOAD_KEY + uuid;

        try {
            //根据业务主键加锁，多个fp服务可以同时上报不同的指令
            Boolean lock = cacheFactory.getLockService().lock(lockKey, expireTime);
            if (!lock) {
                return;
            }

            //获取最新状态，以免其他fp服务已经上报成功
            InstructionPO instructionPO = getByUuid(uuid);

            if (uploadFailed(instructionPO) && instructionPO.getRetryCount() < maxAttempts) {
                //有信息没有上报成功并且没有超过最大尝试次数
                boolean allSuccess = true;
                instructionPO.setRetryCount(instructionPO.getRetryCount() + 1);


                //调用照片类信息上传
                if (!uploadFinished(instructionPO.getFileUploadStatus())) {
                    allSuccess = doUpload(instructionPO, UploadType.File, (requestId, code, iccid) -> {
                        //通过iccid查询实名的记录
                        MgRnrCardInfoDTO card = new MgRnrCardInfoDTO();
                        card.setIccid(iccid);
                        card.setRnrStatus(RnrStatus.RNR.getCode());
                        Response<List<MgRnrCardInfoDTO>> cardListResp = rnrCardInfoClient.queryByList(card);
                        if (!cardListResp.isSuccess() || CollectionUtils.isEmpty(cardListResp.getData())) {
                            log.error("tryUploadInfo 指令上报-通过卡查询卡列表失败， 原因：{}", JSON.toJSONString(cardListResp));
                            return false;
                        }
                        card = cardListResp.getData().get(0);

                        //查询入网合同
                        MgRnrFileDTO rnrFileDTO = new MgRnrFileDTO();
                        rnrFileDTO.setRnrId(card.getRnrId());
                        rnrFileDTO.setTenantNo(card.getTenantNo());
                        rnrFileDTO.setFileType(RnrFileType.VEHUCLE_BIND.getCode());
                        Response<List<MgRnrFileDTO>> rnrFileResp = rnrFileClient.queryByList(rnrFileDTO);
                        if (!rnrFileResp.isSuccess() || CollectionUtils.isEmpty(rnrFileResp.getData())) {
                            log.error("tryUploadInfo 指令上报-查询文件失败， 原因：{}", JSON.toJSONString(rnrFileResp));
                            return false;
                        }

                        //通过code 查询指令上报密钥
                        /*FpT1VehicleCompanyDTO t1VehicleCompanyDTO = new FpT1VehicleCompanyDTO();
                        t1VehicleCompanyDTO.setCompanyCode(code);
                        t1VehicleCompanyDTO = t1VehicleCompanyService.getByCode(t1VehicleCompanyDTO);*/

                        EncryptionKeyDTO encryptionKeyDTO = encryptionConfig.getKey(card.getOrderId());
//                        final FpT1VehicleCompanyDTO t1VehicleCompanyDTO = getFpT1VehicleCompanyDTO(iccid);
                        if (encryptionKeyDTO == null || encryptionKeyDTO.getT1InstructionKey() == null) {
                            log.error("tryUploadInfo 指令上报-通过code查询密钥失败，code：{}", code);
                            return false;
                        }

                        //调用照片类信息上传
                        T1FileReqDTO t1FileReqDTO = new T1FileReqDTO();
                        t1FileReqDTO.setCode(code);
                        t1FileReqDTO.setRequestID(requestId);
                        return fileUploadInstruction(rnrFileResp.getData(), t1FileReqDTO,
                                encryptionKeyDTO, iccid);
                    });
                }

                if (!allSuccess && instructionPO.getRetryCount() >= maxAttempts) {
                    sendWarningMessage(instructionPO);
                }
            }
        } catch (Exception e) {
            log.error("RetryUploadInfo failed, uuid:" + uuid, e);
        } finally {
            unlockKey(lockKey);
        }
    }


    public void tryUploadInfoIssue(String uuid) {
        String lockKey = RedisConstant.RNR_INSTRUCTION_UPLOAD_KEY + uuid;

        try {
            //根据业务主键加锁，多个fp服务可以同时上报不同的指令
            Boolean lock = cacheFactory.getLockService().lock(lockKey, expireTime);
            if (!lock) {
                return;
            }

            //获取最新状态，以免其他fp服务已经上报成功
            InstructionPO instructionPO = getByUuid(uuid);

            if (uploadFailed(instructionPO) && instructionPO.getRetryCount() < maxAttempts) {
                //有信息没有上报成功并且没有超过最大尝试次数
                boolean allSuccess = true;
                instructionPO.setRetryCount(instructionPO.getRetryCount() + 1);

                //调用照片类信息上传
                if (!uploadFinished(instructionPO.getFileUploadStatus())) {
                    allSuccess = doUpload(instructionPO, UploadType.File, (requestId, code, iccid) -> {

                        Response<OrgSimRelDTO> response =orgSimRelClient.getByIccid(iccid);
                        if(!response.isSuccess()||null ==response.getData()){
                            log.error("没有查询到卡信息,iccid为{}",instructionPO.getIccid());
                            return false;
                        }
                        //通过iccid查询实名的记录
                        MgRnrCardInfoDTO card = new MgRnrCardInfoDTO();
                        card.setIccid(iccid);
                        card.setRnrStatus(RnrStatus.RNR.getCode());
                        Response<List<MgRnrCardInfoDTO>> cardListResp = rnrCardInfoClient.queryByList(card);
                        if (!cardListResp.isSuccess() || CollectionUtils.isEmpty(cardListResp.getData())) {
                            log.error("tryUploadInfo 指令上报-通过卡查询卡列表失败， 原因：{}", JSON.toJSONString(cardListResp));
                            return false;
                        }
                        card = cardListResp.getData().get(0);

                        //查询入网合同
                        MgRnrFileDTO rnrFileDTO = new MgRnrFileDTO();
                        rnrFileDTO.setRnrId(card.getRnrId());
                        rnrFileDTO.setTenantNo(card.getTenantNo());
                        rnrFileDTO.setFileType(RnrFileType.VEHUCLE_BIND.getCode());
                        Response<List<MgRnrFileDTO>> rnrFileResp = rnrFileClient.queryByList(rnrFileDTO);
                        if (!rnrFileResp.isSuccess() || CollectionUtils.isEmpty(rnrFileResp.getData())) {
                            log.error("tryUploadInfo 指令上报-查询文件失败， 原因：{}", JSON.toJSONString(rnrFileResp));
                            return false;
                        }

                        //通过code 查询指令上报密钥
                        /*FpT1VehicleCompanyDTO t1VehicleCompanyDTO = new FpT1VehicleCompanyDTO();
                        t1VehicleCompanyDTO.setCompanyCode(code);
                        t1VehicleCompanyDTO = t1VehicleCompanyService.getByCode(t1VehicleCompanyDTO);*/
                        EncryptionKeyDTO encryptionKeyDTO = encryptionConfig.getKey(response.getData().getOrgUuid());
//                        final FpT1VehicleCompanyDTO t1VehicleCompanyDTO = getFpT1VehicleCompanyDTO(iccid);
                        if (encryptionKeyDTO == null || encryptionKeyDTO.getT1InstructionKey() == null) {
                            log.error("tryUploadInfo 指令上报-通过code查询密钥失败，code：{}", code);
                            return false;
                        }

                        //调用照片类信息上传
                        T1FileReqDTO t1FileReqDTO = new T1FileReqDTO();
                        t1FileReqDTO.setCode(encryptionKeyDTO.getCompanyCode());
                        t1FileReqDTO.setRequestID(requestId);
                        t1FileReqDTO.setPlatformID(encryptionKeyDTO.getPlatFormId());
                        return fileUploadInstruction(rnrFileResp.getData(), t1FileReqDTO,
                                encryptionKeyDTO, iccid);
                    });
                }

                if (!allSuccess && instructionPO.getRetryCount() >= maxAttempts) {
                    sendWarningMessage(instructionPO);
                }
            }
        } catch (Exception e) {
            log.error("RetryUploadInfo failed, uuid:" + uuid, e);
        } finally {
            unlockKey(lockKey);
        }
    }
    //-------------------私有方法区-------------------

    /**
     * Description: 获取联通，电信，移动等信息
     * <br />
     * CreateDate 2022-06-15 09:50:30
     *
     * @author zx
     **/
    private final FpT1VehicleCompanyDTO getFpT1VehicleCompanyDTO(String iccid) {
        final FpT1VehicleCompanyDTO fpT1VehicleCompanyDTO = new FpT1VehicleCompanyDTO();
        final MbCodeEnum mbCodeEnum = MbCodeEnum.get(iccid);
        switch (mbCodeEnum) {
            case CMCC:
                fpT1VehicleCompanyDTO.setCompanyCode(chinaMobileT1UploadService.getCode());
                fpT1VehicleCompanyDTO.setT1UserInfoKey(chinaMobileT1UploadService.getT1UserInfoKey());
                fpT1VehicleCompanyDTO.setT1InstructionKey(t1InstructionKey);
                break;
            case CUCC:
                fpT1VehicleCompanyDTO.setCompanyCode(chinaUnicomT1UploadService.getCode());
                fpT1VehicleCompanyDTO.setT1UserInfoKey(chinaUnicomT1UploadService.getT1UserInfoKey());
                fpT1VehicleCompanyDTO.setT1InstructionKey(t1InstructionKey);
                break;
            case CTCC:
                fpT1VehicleCompanyDTO.setCompanyCode(chinaTelecomT1UploadServiceImpl.getCode());
                fpT1VehicleCompanyDTO.setT1UserInfoKey(chinaTelecomT1UploadServiceImpl.getT1UserInfoKey());
                fpT1VehicleCompanyDTO.setT1InstructionKey(t1InstructionKey);
                break;
        }

        return fpT1VehicleCompanyDTO;
    }


    /**
     * Description: 文件信息上报
     * <br />
     * CreateDate 2022-06-08 09:50:30
     *
     * @author yuyi
     **/
    private boolean fileUploadInstruction(List<MgRnrFileDTO> list, T1FileReqDTO t1FileReqDTO, EncryptionKeyDTO encryptionKeyDTO, String iccid) {
        //从文件系统下载文件到本地目录
        String folderName = "HT_" + t1FileReqDTO.getRequestID() + "_" + t1FileReqDTO.getCode();
        String folderPath = System.getProperty("java.io.tmpdir") + File.separator + folderName;
        File rootFile = new File(folderPath);
        if (!rootFile.exists()) {
            rootFile.mkdirs();
        }
        FileDownloadDTO fileDownload;
        StringBuffer sbr;
        for (int i = 0; i < list.size(); i++) {
            sbr = new StringBuffer(folderName);
            sbr.append("_");
            sbr.append(String.format("%03d", i+1)) ;
            /*//通过文件id查询文件格式
            FileRecordDTO fileRecord = fileService.fileMetainfo(list.get(i).getFileSystemId());
            if (fileRecord == null) {
                log.error("文件【{}】未查询到文件信息", list.get(i).getFileSystemId());
                continue;
            }*/
            //增加文件类型后缀
            final String fileSystemId = list.get(i).getFileSystemId();
            sbr.append(fileSystemId.substring(fileSystemId.lastIndexOf(".")));
            fileDownload = new FileDownloadDTO();
            fileDownload.setFilePath(folderPath);
            fileDownload.setUuid(fileSystemId);
            fileDownload.setFileName(sbr.toString());
            //fileService.downloadToLocal(fileDownload);


			try {
				byte[] bytes = fileService.downLoadBytes(fileSystemId);
				FileUtils.writeByteArrayToFile(
						new File(fileDownload.getFilePath() + File.separator + fileDownload.getFileName()),
						bytes);
			} catch (IOException e) {
				log.error("FileUtils.writeByteArrayToFile error  ", e);
			}
        }

        //压缩目录并加密，将压缩文件读取到EncryptFile中
        String zipFileName = folderPath + ".zip";
        try {
            if (com.cusc.nirvana.user.rnr.fp.constants.MbCodeEnum.CMCC.getCode().equals(operatorService.get(iccid))) {
                ZipUtils.toZip(folderPath, new FileOutputStream(zipFileName), false);

                File file = it1UploadService.encryptZipFile(new File(zipFileName),encryptionKeyDTO);
                t1FileReqDTO.setEncryptFile(file);
                cmccSftpUtil.upload(t1FileReqDTO);

                return true;
            }else {
                ZipUtils.toZip(folderPath, new FileOutputStream(zipFileName), false);
                //移动，联通，电信加密算法不同
                final File file = encryptZipFile(iccid, zipFileName, folderPath);
                t1FileReqDTO.setEncryptFile(file);
                final T1CommonResponseDTO t1CommonResponseDTO = uploadInstructionT1File(t1FileReqDTO, iccid);
                return true;
            }


        } catch (Exception e) {
            log.error("ZipUtils toZip error ", e);
            return false;
        } finally {
            //删除文件夹
            CuscFileUtils.delFolder(folderPath);

            //删除zip文件
            File zipFile = new File(zipFileName);
            if (zipFile.exists()) {
                zipFile.delete();
            }
            //删除bin文件
            File binFile = new File(folderPath + ".bin");
            if (binFile.exists()) {
                binFile.delete();
            }
        }
    }

    private final File encryptZipFile(String iccid, String zipFileName, String folderPath) {
        final MbCodeEnum mbCodeEnum = MbCodeEnum.get(iccid);
        switch (mbCodeEnum) {
            case CMCC:
                return chinaMobileT1UploadService.encryptZipFile(zipFileName, folderPath);
            case CUCC:
                return chinaUnicomT1UploadService.encryptZipFile(zipFileName, folderPath);
            case CTCC:
                return chinaTelecomT1UploadServiceImpl.encryptZipFile(zipFileName, folderPath);
            default:
                return null;
        }

    }

    /**
     * Description: 上传开户信息
     * <br />
     * CreateDate 2022-05-16 10:22:07
     *
     * @author yuyi
     **/
    private T1CommonResponseDTO uploadInstructionT1File(T1FileReqDTO file, String iccid) {
        final MbCodeEnum mbCodeEnum = MbCodeEnum.get(iccid);
        switch (mbCodeEnum) {
            case CMCC:
                return chinaMobileT1UploadService.uploadT1FileByInstruction(file);
            case CUCC:
                return chinaUnicomT1UploadService.uploadT1File(file);
            case CTCC:
                return chinaTelecomT1UploadServiceImpl.uploadT1File(file);
            default:
                return null;
        }
    }

    /**
     * 更新上传状态，并插入日志
     *
     * @param instructionPO
     * @param uploadType
     * @param status
     */
    private void uploadStatusAndSaveLog(InstructionPO instructionPO, UploadType uploadType, UploadStatus status) {
        switch (uploadType) {
            case File:
                instructionPO.setFileUploadStatus(status.getCode());
                break;
            case User:
                instructionPO.setUserUploadStatus(status.getCode());
                break;
            case Vehicle:
                instructionPO.setVehicleUploadStatus(status.getCode());
                break;
            default:
                break;
        }
        baseMapper.updateById(instructionPO);
        iInstructionLogService.insertLog(instructionPO, uploadType, status);
    }

    /**
     * 上传信息
     *
     * @param instructionPO 指令信息
     * @param uploadType    上传类型
     * @param handler       上报执行方式
     */
    private boolean doUpload(InstructionPO instructionPO, UploadType uploadType, UploadHandler handler) {
        if (handler.upload(instructionPO.getRequestId(), instructionPO.getCode(), instructionPO.getIccid())) {
            uploadStatusAndSaveLog(instructionPO, uploadType, UploadStatus.Success);
            return true;
        } else {
            uploadStatusAndSaveLog(instructionPO, uploadType, UploadStatus.Failed);
            return false;
        }
    }

    /**
     * 判断是有信息没有上传成功
     */
    private boolean uploadFailed(InstructionPO instructionPO) {
        return !uploadFinished(instructionPO.getUserUploadStatus()) || !uploadFinished(
                instructionPO.getFileUploadStatus());
    }

    /**
     * 释放redis锁
     *
     * @param lockKey key
     */
    private void unlockKey(String lockKey) {
        try {
            cacheFactory.getLockService().unLock(lockKey);
        } catch (CacheException e) {
            log.error("Failed to unlock " + lockKey, e);
        }
    }

    /**
     * 查询所有失败并且未到最大重试次数的请求，按照请求先后顺序进行重试
     */
    private void findFailedJobAndUpload() {
        Iterator<InstructionPO> its = findFailedAndRetryableInstructions();
        while (its.hasNext()) {
            InstructionPO next = its.next();
            tryUploadInfo(next.getUuid());
        }
    }

    /**
     * 是否已经上传成功
     *
     * @param status 上报状态
     */
    private boolean uploadFinished(Integer status) {
        return status != null && status == 1;
    }

    /**
     * @return 未完成指令迭代器
     */
    private Iterator<InstructionPO> findFailedAndRetryableInstructions() {
        return new Iterator<InstructionPO>() {
            private Page<InstructionPO> instructions;
            int currentPage = 1;
            int currentIndex = 0;

            @Override
            public boolean hasNext() {
                if (instructions != null && currentIndex < instructions.getRecords().size()) {
                    return true;
                }
                if (instructions == null) {
                    instructions = findNexPage(currentPage++);
                    currentIndex = 0;
                } else if (currentIndex >= instructions.getRecords().size()) {
                    currentIndex = 0;
                    if (instructions.hasNext()) {
                        instructions = findNexPage(currentPage++);
                    } else {
                        instructions = null;
                    }
                }
                return instructions != null && !instructions.getRecords().isEmpty();
            }

            @Override
            public InstructionPO next() {
                return instructions.getRecords().get(currentIndex++);
            }
        };
    }

    /**
     * 分页查询数据
     * 查询 1.上报失败并且没有超过重试次数的执行
     *
     * @param page 当前页
     * @return 页数据
     */
    private Page<InstructionPO> findNexPage(int page) {
        QueryWrapper<InstructionPO> queryWrapper = new QueryWrapper<>();
        //retry_count < maxAttempts
        // and (user_upload_status != 1 or vehicle_upload_status !=1 or file_upload_status !=1)
        queryWrapper.lt("retry_count", maxAttempts);
        queryWrapper.and(wrapper -> wrapper.ne("file_upload_status", 1));
        queryWrapper.orderByAsc("create_time");
        return this.page(new Page<>(page, 100), queryWrapper);
    }

    /**
     * 上报处理类
     *
     * @author yubo
     * @since 2022-04-15 10:41
     */
    private interface UploadHandler {
        //处理上报信息
        boolean upload(String requestId, String code, String iccid);
    }
}
