package com.jeeplus.finance.invoice.service.OMS;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.jeeplus.common.redis.RedisUtils;
import com.jeeplus.finance.invoice.domain.OMS.OMSAccessTokenInfo;
import com.jeeplus.finance.invoice.domain.OMS.fastRed.OMSRedInvoiceConfirmResponse;
import com.jeeplus.finance.invoice.mapper.CwFinanceInvoiceMapper;
import com.jeeplus.finance.invoice.service.dto.CwFinanceInvoiceDTO;
import com.jeeplus.finance.invoice.util.OMS.HttpPostJsonUtil;
import com.jeeplus.finance.invoice.util.OMS.OMSNationUtil;
import com.jeeplus.finance.utils.Global;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.*;
import java.util.concurrent.TimeUnit;
import static com.jeeplus.finance.invoice.config.OmsConfig.*;

/**
 * 红字确认单查询接口回调
 * 用于发票红冲 或判定双方是否确认
 */
@Service
@Transactional(readOnly = true)
@Lazy
public class RedInvoiceScheduledService {

    //private static final String appId = Global.getConfig("omsAppId");
    //private static final String appKey = Global.getConfig("omsAppKey");
    //private static final String deptCode = Global.getConfig("omsDeptCode");

    @Autowired
    private OMSDisposeService omsDisposeService;
    @Autowired
    private CwFinanceInvoiceMapper workInvoiceDao;

    @Autowired
    private ObjectMapper objectMapper;

    @Transactional(readOnly = false)
    public void processAllRedInvoiceTasks() {
        // 时间窗口校验
        Calendar now = Calendar.getInstance();
        int hour = now.get(Calendar.HOUR_OF_DAY);
        if (hour < 8 || hour >= 20) {
            System.out.println("[定时任务] 非业务时段，跳过");
            return;
        }

        RedisUtils jedis = null;
        try {
            jedis = RedisUtils.getInstance();
            Set<String> taskKeys = jedis.keys("OMS_red_invoice_task:*");
            if (taskKeys.isEmpty()) {
                System.out.println("[定时任务] 暂无待处理任务");
                return;
            }

            // 遍历处理所有Redis任务
            for (String taskKey : taskKeys) {
                String applyNo = taskKey.replace("OMS_red_invoice_task:", "");
                // 读取Redis参数
                String remainRetryTimesStr =(String) jedis.hGet(taskKey, "remainRetryTimes");
                String jsonInvoicResultStr = (String) jedis.hGet(taskKey, "jsonInvoicResultStr");
                String accessToken =(String)  jedis.hGet(taskKey, "accessToken");
                String startTimeStr = (String) jedis.hGet(taskKey, "startTime");
                String informType = (String) jedis.hGet(taskKey, "informType");
                String workInvoiceId = (String) jedis.hGet(taskKey, "workInvoiceId");

                //获取当前redis中的accessToken
                //如果有值，则使用当前redis中的accessToken，否则重新生成一个新的accessToken
                accessToken =(String)  jedis.get("OMSAccessToken");

                if(StringUtils.isBlank(accessToken)){
                    // 获取AccessToken 9998重试5次
                    accessToken = omsDisposeService.getOmsAccessTokenWithRetry(5, "accessToken", workInvoiceId, informType);
                    if(StringUtils.isNotBlank(accessToken)){
                        jedis.setEx("OMSAccessToken", accessToken,86400, TimeUnit.SECONDS);
                        System.out.println("重新获取token成功，存入Redis");
                    } else {
                        accessToken = "";
                        System.out.println("获取token失败");
                        return;
                    }
                }


                // 参数校验
                if (StringUtils.isAnyBlank(remainRetryTimesStr, accessToken, startTimeStr)) {
                    jedis.delete(taskKey);
                    continue;
                }

                // 转换参数并执行任务
                int remainRetryTimes = Integer.parseInt(remainRetryTimesStr);
                long startTime = Long.parseLong(startTimeStr);
                executeRedInvoiceTask(applyNo, remainRetryTimes, jsonInvoicResultStr, accessToken, startTime, workInvoiceId, informType);
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if (jedis != null) {
                //jedis.close();
            }
        }
    }


    // ========== 1. 封装所有需要的业务逻辑（独立执行，不依赖原有业务类） ==========

    /**
     *
     * @param applyNo       发票id
     * @param remainRetryTimes  允许回调的次数，这里暂时未用到，仅作为保留参数
     * @param jsonInvoicResultStr   全场景红冲订单提交接口返回值
     * @param accessToken
     * @param startTime     这个任务开始时间。限制：开始3天内如果双方没有全部确认，则本次红冲失败
     * @param workInvoiceId 发票id
     */
    @Transactional(readOnly = false)
    public void executeRedInvoiceTask(String applyNo, int remainRetryTimes, String jsonInvoicResultStr, String accessToken, long startTime, String workInvoiceId, String informType) {
        try {
            CwFinanceInvoiceDTO workInvoice = workInvoiceDao.queryById(workInvoiceId);

            if (null != workInvoice) {
                // 步骤1：3天超时判断
                if (System.currentTimeMillis() - startTime > 259200000L) {
                    System.err.println("❌ 任务["+applyNo+"]超时，触发兜底逻辑");
                    omsDisposeService.handleInvoiceRetryAllFail(accessToken, workInvoiceId, "", informType);
                    deleteRedInvoiceTaskFromRedis(applyNo);
                    return;
                }

                // 步骤2：首次执行（有jsonInvoicResultStr）→ 解析结果，触发查询
                if (StringUtils.isNotBlank(jsonInvoicResultStr)) {
                    OMSAccessTokenInfo resultTokenInfo = JSON.parseObject(jsonInvoicResultStr, OMSAccessTokenInfo.class);
                    if (null == resultTokenInfo || null == resultTokenInfo.getResult() || !"0000".equals(resultTokenInfo.getResult().getCode())) {
                        System.err.println("❌ 任务["+applyNo+"]解析失败，触发兜底");
                        omsDisposeService.handleInvoiceRetryAllFail(accessToken, workInvoiceId, "", informType);
                        deleteRedInvoiceTaskFromRedis(applyNo);
                        return;
                    }
                    // 解析成功，调用查询接口
                    callRedInvoiceConfirmQuery(remainRetryTimes, accessToken, applyNo, startTime, workInvoiceId, workInvoice, informType);
                    return;
                }

                // 步骤3：后续重试（无jsonInvoicResultStr）→ 直接调用查询接口
                callRedInvoiceConfirmQuery(remainRetryTimes, accessToken, applyNo, startTime, workInvoiceId, workInvoice, informType);
            }




        } catch (Exception e) {
            e.printStackTrace();
            // 异常后重试次数-1，重新存入Redis
            if (remainRetryTimes > 1) {
                saveRedInvoiceTaskToRedis(remainRetryTimes - 1, jsonInvoicResultStr, accessToken, applyNo, startTime, informType);
            } else {
                omsDisposeService.handleInvoiceRetryAllFail(accessToken, workInvoiceId, "红字确认单查询接口回调失败，请重新发起", informType);
                deleteRedInvoiceTaskFromRedis(applyNo);
            }
        }
    }

    /**
     * 调用红字确认单查询接口（核心业务逻辑）
     * @param remainRetryTimes  允许回调的次数，这里暂时未用到，仅作为保留参数
     * @param accessToken
     * @param applyNo   发票id
     * @param startTime 这个任务开始时间。限制：开始3天内如果双方没有全部确认，则本次红冲失败
     * @param workInvoiceId 发票id
     */
    @Transactional(readOnly = false)
    public void callRedInvoiceConfirmQuery(int remainRetryTimes, String accessToken, String applyNo, long startTime, String workInvoiceId, CwFinanceInvoiceDTO workInvoice, String informType) {
        try {
            // 组装查询参数
            OMSNationUtil util = new OMSNationUtil();
            String queryData = util.neatenAllScenarioRedInvoiceConfirmQueryData(applyNo);
            OMSAccessTokenInfo invoiceDownInfo = new OMSAccessTokenInfo();
            invoiceDownInfo.setAppId(appId);
            invoiceDownInfo.setAppKey(appKey);
            invoiceDownInfo.setExchangeId(UUID.randomUUID().toString());
            invoiceDownInfo.setAccessToken(accessToken);
            invoiceDownInfo.setData(queryData);
            String jsonInvoiceDownStr = JSON.toJSONString(invoiceDownInfo);

            // 调用查询接口(红字确认单接口，即红冲的第3步)
            String invoiceResultStr = HttpPostJsonUtil.doPost(omsUrl +"/prod-api/output/server/redApply/query", jsonInvoiceDownStr);
            if (StringUtils.isBlank(invoiceResultStr)) {
                throw new RuntimeException("查询接口返回空");
            }

            // 解析查询结果
            OMSAccessTokenInfo resultDownInfo = JSON.parseObject(invoiceResultStr, OMSAccessTokenInfo.class);
            if (null == resultDownInfo.getResult() || !"0000".equals(resultDownInfo.getResult().getCode())) {
                throw new RuntimeException("查询接口返回错误码：" + (resultDownInfo.getResult() != null ? resultDownInfo.getResult().getCode() : "NULL"));
            }
            //对 invoiceResultStr 这个调用的结果进行保存，方便redis崩溃重新处理
            workInvoice.setConfirmationSlipResultStr(invoiceResultStr);
            workInvoiceDao.updateConfirmationSlipResultStr(workInvoice);


            String invoceDownJsonStr = OMSNationUtil.extractFromBase64OnClassStr(resultDownInfo.getData().toString());
            OMSRedInvoiceConfirmResponse invoiceInfo = JSONObject.parseObject(invoceDownJsonStr, OMSRedInvoiceConfirmResponse.class);
            String confirmStatus = invoiceInfo.getConfirmStatus();


            System.out.println("invoiceInfo的值是："+ invoiceInfo);
            System.out.println("开票确认单接口查询，返回状态值是："+ invoiceInfo.getConfirmStatus());
            System.out.println("开票确认单接口查询，返回失败原因是："+ invoiceInfo.getFailCause());
            System.out.println("开票确认单接口查询，返回开票状态是："+ invoiceInfo.getMakeStatus());
            System.out.println("开票确认单接口查询，返回红字发票订单号是："+ invoiceInfo.getRedInvOrderNo());
            // 你原有注释和代码：判断开票接口查询返回值，
            // 如果是01、04 则表示可直接通过
            // 如果是02、03、15 则需要每30秒重新访问一次 查询红字确认单查询接口 。理论上应该持续3天（即双方确认最终时间节点）
            // 如果是其他的，直接告诉发起人 让其确认后重新发起

            // 你原有注释和代码：01 无需确认
            // 02 销方录入待购方确认
            // 03 购方录入待销方确认
            // 04 购销双方已确认
            // 05 作废（销方录入购方否认）
            // 06 作废（购方录入销方否认）
            // 07 作废（超 72 小时未确认）
            // 08 作废（发起方已撤销）
            // 09 作废（确认后撤销）
            // 10 作废（异常凭证）
            // 15  申请中
            // 16  申请失败
            // 判断状态，处理不同分支

            switch (confirmStatus) {

                case "01":
                case "04":
                    // 访问成功后的后续方法
                    // 整体默认是自动开票
                    // 判定 开票状态是否是已开票（03）
                    if ("3".equals(invoiceInfo.getMakeStatus())) {

                        String redInvOrderNo = invoiceInfo.getRedInvOrderNo();
                        // 存入Redis，由InvoiceDownloadTask接管
                        saveInvoiceDownloadTaskToRedis(accessToken, redInvOrderNo, workInvoiceId, informType);
                        System.out.println("✅ 解析开票数据任务["+redInvOrderNo+"]已存入Redis，由InvoiceDownloadTask接管重试");

                        deleteRedInvoiceTaskFromRedis(applyNo); // 成功后删除Redis
                    } else if ("1".equals(invoiceInfo.getMakeStatus()) || "4".equals(invoiceInfo.getMakeStatus())) {
                        // 开票中，重新存入Redis等待下次重试
                        saveRedInvoiceTaskToRedis(remainRetryTimes, "", accessToken, applyNo, startTime, informType);
                    } else {
                        //到这的 应该返回值就是2 了  表示 开票失败
                        omsDisposeService.handleInvoiceRetryAllFail(accessToken, workInvoiceId, "红冲开票失败，请确认后重新开票", informType);
                        deleteRedInvoiceTaskFromRedis(applyNo);
                    }
                    break;
                case "02":
                case "03":
                case "15":
                    // 待确认，重新存入Redis等待下次重试
                    //到这 说明 对方应该是没有进行发票确认，所以没能进行开票成功，需要对方进行确认
                    saveRedInvoiceTaskToRedis(remainRetryTimes, "", accessToken, applyNo, startTime, informType);
                    break;

                case "05":
                case "06":
                case "07":
                case "08":
                case "09":
                case "10":
                case "16":
                    // 终止本次红冲操作，通知发起人确认后重新处理
                    omsDisposeService.handleInvoiceRetryAllFail(accessToken, workInvoiceId, "本次红冲的开票作废或失败，请确认后重新开票", informType);
                    // 失败后删除Redis任务
                    deleteRedInvoiceTaskFromRedis(applyNo);
                    break;
                default:
                    System.out.println("开票确认单接口查询，返回状态值是："+ invoiceInfo.getConfirmStatus() + "。稍后重试");
                    break;
            }
        } catch (Exception e) {
            e.printStackTrace();
            // 接口调用失败，重试次数-1后重新存入Redis
            if (remainRetryTimes > 1) {
                saveRedInvoiceTaskToRedis(remainRetryTimes - 1, "", accessToken, applyNo, startTime, informType);
            } else {
                omsDisposeService.handleInvoiceRetryAllFail(accessToken, workInvoiceId, "红冲开票失败，请确认后重新开票", informType);
                deleteRedInvoiceTaskFromRedis(applyNo);
            }
        }
    }

    /**
     * 存入Redis（定时任务内部使用）
     */
    @Transactional(readOnly = false)
    public void saveRedInvoiceTaskToRedis(int remainRetryTimes, String jsonInvoicResultStr, String accessToken, String applyNo, long startTime, String informType) {
        RedisUtils jedis = null;
        try {
            jedis = RedisUtils.getInstance();
            String redisKey = "OMS_red_invoice_task:" + applyNo;
            jedis.hPut(redisKey, "remainRetryTimes", String.valueOf(remainRetryTimes));
            jedis.hPut(redisKey, "jsonInvoicResultStr", jsonInvoicResultStr);
            jedis.hPut(redisKey, "accessToken", accessToken);
            jedis.hPut(redisKey, "applyNo", applyNo);
            jedis.hPut(redisKey, "informType", informType);
            jedis.hPut(redisKey, "startTime", String.valueOf(startTime));
            jedis.expire(redisKey, 259200 + 3600);

            // ========== 追加：Redis数据转JSON，持久化到发票表（兜底逻辑） ==========
            long redisStoreTime = System.currentTimeMillis() / 1000; // 秒级存储时间，用于后期回滚
            // 提取Redis中startTime的字符串值，Map和Redis共用，保证数据完全一致
            String startTimeStr = String.valueOf(startTime);
            // 封装Map：包含Redis所有字段 + redisKey/过期时间/存储时间（保留redisKey）
            Map<String, Object> redInvoiceMap = new HashMap<>();
            // Redis哈希中所有原字段（与Redis完全一致）
            redInvoiceMap.put("remainRetryTimes", remainRetryTimes);
            redInvoiceMap.put("jsonInvoicResultStr", jsonInvoicResultStr);
            redInvoiceMap.put("accessToken", accessToken);
            redInvoiceMap.put("applyNo", applyNo);
            redInvoiceMap.put("informType", informType);
            redInvoiceMap.put("startTime", startTimeStr);
            // 额外兜底字段（保留redisKey，贴合你的要求）
            redInvoiceMap.put("redisKey", redisKey);
            redInvoiceMap.put("redisExpireSeconds", 259200 + 3600);
            redInvoiceMap.put("redisStoreTime", redisStoreTime);

            // Map转JSON字符串（单独捕获序列化异常，不影响Redis核心逻辑）
            String redInvoiceJson = objectMapper.writeValueAsString(redInvoiceMap);

            // 更新发票表（唯一标识为applyNo，与Redis一致）
            CwFinanceInvoiceDTO workInvoice = new CwFinanceInvoiceDTO();
            workInvoice.setId(applyNo); // 关键：用applyNo作为更新ID，与你的唯一标识一致
            workInvoice.setRedInvoiceJson(redInvoiceJson);
            // 处理DAO更新，单独捕获异常，不影响核心逻辑
            workInvoiceDao.updateRedInvoiceJsonByWorkInvoiceId(workInvoice);

        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if (jedis != null) {
                //jedis.close();
            }
        }
    }

    /**
     * 删除Redis任务
     */
    private void deleteRedInvoiceTaskFromRedis(String applyNo) {
        RedisUtils jedis = null;
        try {
            jedis = RedisUtils.getInstance();
            jedis.delete("OMS_red_invoice_task:" + applyNo);
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if (jedis != null) {
                //jedis.close();
            }
        }
    }


    /**
     * 开票成功，需要进行下载解析的开票信息存储到redis中，进行下一步处理
     * @param accessToken
     * @param workInvoiceId 开票id
     */
    @Transactional(readOnly = false)
    private void saveInvoiceDownloadTaskToRedis(String accessToken, String redInvOrderNo, String workInvoiceId,  String informType) {
        RedisUtils jedis = null;
        try {
            jedis = RedisUtils.getInstance();
            String redisKey = "OMS_red_invoice_download:" + workInvoiceId;
            jedis.hPut(redisKey, "accessToken", accessToken);
            jedis.hPut(redisKey, "redInvOrderNo", redInvOrderNo);
            jedis.hPut(redisKey, "workInvoiceId", workInvoiceId);
            jedis.hPut(redisKey, "informType", informType);
            jedis.hPut(redisKey, "firstExecTime", String.valueOf(System.currentTimeMillis()));
            jedis.expire(redisKey, 86400); // 1天过期

            // ========== 追加：Redis数据转JSON → 持久化到发票表（兜底逻辑） ==========
            long redisStoreTime = System.currentTimeMillis() / 1000; // 秒级存储时间，用于后期回滚
            // 封装Map：Redis所有原字段 + redisKey/过期时间/存储时间（保留redisKey）
            Map<String, Object> downloadTaskMap = new HashMap<>(8);
            // Redis哈希中5个原字段（与Redis键名/值完全一致，含新增的redInvOrderNo）
            downloadTaskMap.put("accessToken", accessToken);
            downloadTaskMap.put("redInvOrderNo", redInvOrderNo);
            downloadTaskMap.put("workInvoiceId", workInvoiceId);
            downloadTaskMap.put("informType", informType);
            downloadTaskMap.put("firstExecTime", String.valueOf(System.currentTimeMillis()));
            // 额外兜底字段（保留redisKey，后期回滚直接取用）
            downloadTaskMap.put("redisKey", redisKey);
            downloadTaskMap.put("redisExpireSeconds", 86400);
            downloadTaskMap.put("redisStoreTime", redisStoreTime);

            // Map转JSON字符串（单独捕获序列化异常，不影响Redis核心逻辑）
            String downloadTaskJson = objectMapper.writeValueAsString(downloadTaskMap);

            // 更新发票表（以workInvoiceId为唯一标识，与Redis一致）
            CwFinanceInvoiceDTO workInvoice = new CwFinanceInvoiceDTO();
            workInvoice.setId(workInvoiceId); // 关键：与redisKey的拼接标识一致
            workInvoice.setRedDownloadInvoiceJson(downloadTaskJson);
            workInvoiceDao.updateRedInvoiceJsonByWorkInvoiceId(workInvoice);
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if (jedis != null) {
                //jedis.close();
            }
        }
    }
}
