package com.seer.teach.pay.app.notify.job;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.seer.teach.common.enums.pay.PayNotifyTaskStatusEnum;
import com.seer.teach.common.enums.pay.PayNotifyTypeEnum;
import com.seer.teach.common.enums.pay.PayOrderSourceEnum;
import com.seer.teach.pay.app.notify.service.OrderPayNotifyHandler;
import com.seer.teach.pay.app.notify.service.OrderProcessStrategyFactory;
import com.seer.teach.pay.entity.PayNotifyLogEntity;
import com.seer.teach.pay.entity.PayNotifyTaskEntity;
import com.seer.teach.pay.entity.PayOrderEntity;
import com.seer.teach.pay.entity.RefundOrderEntity;
import com.seer.teach.pay.service.IPayNotifyLogService;
import com.seer.teach.pay.service.IPayNotifyTaskService;
import com.seer.teach.pay.service.IPayOrderService;
import com.seer.teach.pay.service.IRefundOrderService;
import com.xxl.job.core.handler.annotation.XxlJob;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;

import java.time.LocalDateTime;
import java.util.List;

/**
 * 支付通知重试任务
 * 定时扫描失败的通知任务并重新发送通知
 */
@Slf4j
@Component
@RequiredArgsConstructor
public class PayNotifyRetryJob {

    private final IPayNotifyTaskService payNotifyTaskService;

    private final IPayNotifyLogService payNotifyLogService;

    private final IPayOrderService payOrderService;

    private final IRefundOrderService refundOrderService;

    private final OrderProcessStrategyFactory orderProcessStrategyFactory;

    /**
     * 每5分钟执行一次通知重试任务
     */
    @XxlJob("payNotifyRetryJob")
    public void retryNotifyTask() {
        log.info("开始执行支付通知重试任务");

        try {
            LocalDateTime now = LocalDateTime.now();
            List<PayNotifyTaskEntity> notifyTasks = payNotifyTaskService.list(new LambdaQueryWrapper<PayNotifyTaskEntity>()
                    .eq(PayNotifyTaskEntity::getStatus, PayNotifyTaskStatusEnum.PROCESSING.getCode())
                    .le(PayNotifyTaskEntity::getNextNotifyTime, now)
                    .apply("notify_times < max_notify_times")
                    .orderByAsc(PayNotifyTaskEntity::getNextNotifyTime));

            log.info("找到 {} 个需要重试的通知任务", notifyTasks.size());

            int maxProcessCount = 100;
            int processedCount = 0;
            for (PayNotifyTaskEntity notifyTask : notifyTasks) {
                if (processedCount >= maxProcessCount) {
                    log.info("已达到单次处理上限 {}，剩余任务下次处理", maxProcessCount);
                    break;
                }
                try {
                    retryNotify(notifyTask);
                    processedCount++;
                } catch (Exception e) {
                    log.error("处理通知任务失败，任务ID: {}", notifyTask.getId(), e);
                }
            }

            log.info("支付通知重试任务执行完成，本次处理 {} 个任务", processedCount);
        } catch (Exception e) {
            log.error("执行支付通知重试任务时发生异常", e);
        }
    }

    /**
     * 重试通知
     *
     * @param notifyTask 通知任务
     */
    private void retryNotify(PayNotifyTaskEntity notifyTask) {
        log.info("开始重试通知任务，任务ID: {}", notifyTask.getId());

        try {
            int notifyTimes = notifyTask.getNotifyTimes() + 1;
            notifyTask.setNotifyTimes(notifyTimes);
            notifyTask.setLastExecuteTime(LocalDateTime.now());

            PayNotifyTypeEnum notifyType = PayNotifyTypeEnum.fromCode(notifyTask.getType());
            if (notifyType == null) {
                log.warn("未知的通知类型: {}", notifyTask.getType());
                handleNotifyFailure(notifyTask, "未知的通知类型: " + notifyTask.getType());
                return;
            }
            handleNotifyWithStrategy(notifyTask, notifyType);
        } catch (Exception e) {
            log.error("重试通知任务时发生异常，任务ID: {}", notifyTask.getId(), e);
            handleNotifyFailure(notifyTask, "处理通知任务时发生异常: " + e.getMessage());
        }
    }

    /**
     * 处理通知失败的情况
     *
     * @param notifyTask   通知任务
     * @param errorMessage 错误信息
     */
    private void handleNotifyFailure(PayNotifyTaskEntity notifyTask, String errorMessage) {
        try {
            // 更新任务状态为失败
            if (notifyTask.getNotifyTimes() >= notifyTask.getMaxNotifyTimes()) {
                notifyTask.setStatus(PayNotifyTaskStatusEnum.FAILED.getCode());
            } else {
                long delayMinutes = (long) Math.pow(2, notifyTask.getNotifyTimes() - 1);
                notifyTask.setNextNotifyTime(LocalDateTime.now().plusMinutes(delayMinutes));
            }
            payNotifyTaskService.updateById(notifyTask);

            // 记录日志
            PayNotifyLogEntity notifyLog = new PayNotifyLogEntity();
            notifyLog.setTaskId(notifyTask.getId());
            notifyLog.setNotifyTimes(notifyTask.getNotifyTimes());
            notifyLog.setStatus(PayNotifyTaskStatusEnum.FAILED.getCode());
            notifyLog.setResponse(String.format("响应: 通知处理失败, 错误信息: %s", errorMessage));
            payNotifyLogService.save(notifyLog);
        } catch (Exception e) {
            log.error("记录通知失败日志时发生异常，任务ID: {}", notifyTask.getId(), e);
        }
    }

    /**
     * 使用策略模式处理通知任务
     *
     * @param notifyTask 通知任务
     * @param notifyType 通知类型
     */
    private void handleNotifyWithStrategy(PayNotifyTaskEntity notifyTask, PayNotifyTypeEnum notifyType) {
        PayOrderEntity payOrder = null;
        RefundOrderEntity refundOrder = null;
        try {
            // 根据通知类型获取相应的订单信息
            switch (notifyType) {
                case MALL_PAY_SUCCESS:
                case MALL_PAY_FAIL:
                case COIN_RECHARGE_SUCCESS:
                case COIN_RECHARGE_FAIL:
                    payOrder = payOrderService.getById(notifyTask.getDataId());
                    if (payOrder == null) {
                        log.warn("支付订单不存在，订单ID: {}", notifyTask.getDataId());
                        handleNotifyFailure(notifyTask, "支付订单不存在，订单ID: " + notifyTask.getDataId());
                    }
                    break;

                case MALL_REFUND_SUCCESS:
                case MALL_REFUND_FAIL:
                case COIN_REFUND_SUCCESS:
                case COIN_REFUND_FAIL:
                    refundOrder = refundOrderService.getById(notifyTask.getDataId());
                    if (refundOrder == null) {
                        log.warn("退款订单不存在，订单ID: {}", notifyTask.getDataId());
                        handleNotifyFailure(notifyTask, "退款订单不存在，订单ID: " + notifyTask.getDataId());
                    }
                    // 获取支付订单以确定订单来源
                    payOrder = payOrderService.getOrderByOrderSn(refundOrder.getPayOrderSn());
                    if (payOrder == null) {
                        log.warn("支付订单不存在，订单ID: {}", refundOrder.getPayOrderSn());
                        handleNotifyFailure(notifyTask, "支付订单不存在，订单ID: " + refundOrder.getPayOrderSn());
                    }
                    break;
                default:
                    log.warn("不支持的通知类型: {}", notifyType);
                    handleNotifyFailure(notifyTask, "不支持的通知类型: " + notifyType);
            }

            // 根据订单来源获取处理策略
            PayOrderSourceEnum orderSource = PayOrderSourceEnum.fromCode(payOrder.getOrderSource());
            if (orderSource == null) {
                log.warn("未知的订单来源，订单来源代码: {}", payOrder.getOrderSource());
                handleNotifyFailure(notifyTask, "未知的订单来源，订单来源代码: " + payOrder.getOrderSource());
            }
            OrderPayNotifyHandler handler = orderProcessStrategyFactory.getStrategy(orderSource);
            if (handler == null) {
                log.warn("未找到订单来源 {} 的处理策略", orderSource);
                return;
            }
            // 使用策略处理通知
            switch (notifyType) {
                case MALL_PAY_SUCCESS:
                case COIN_RECHARGE_SUCCESS:
                    handler.orderPayAfterSuccess(payOrder);
                    break;
                case MALL_PAY_FAIL:
                case COIN_RECHARGE_FAIL:
                    handler.orderPayAfterFail(payOrder);
                    break;
                case MALL_REFUND_SUCCESS:
                case COIN_REFUND_SUCCESS:
                    handler.orderRefundAfterSuccess(refundOrder);
                    break;
                case MALL_REFUND_FAIL:
                case COIN_REFUND_FAIL:
                    handler.orderRefundAfterFail(refundOrder);
                    break;
            }

        } catch (Exception e) {
            log.error("使用策略模式处理通知任务时发生异常，任务ID: {}", notifyTask.getId(), e);
        }
    }
}