package com.example.demo.service;

import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.LinkedBlockingQueue;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

import com.example.demo.model.ImageToVideoTask;
import com.example.demo.model.PointsFreezeRecord;
import com.example.demo.model.StoryboardVideoTask;
import com.example.demo.model.TaskQueue;
import com.example.demo.model.TaskStatus;
import com.example.demo.model.TextToVideoTask;
import com.example.demo.model.UserWork;
import com.example.demo.model.enums.CommonTaskType;
import com.example.demo.repository.ImageToVideoTaskRepository;
import com.example.demo.repository.StoryboardVideoTaskRepository;
import com.example.demo.repository.TaskQueueRepository;
import com.example.demo.repository.TextToVideoTaskRepository;
import com.fasterxml.jackson.databind.ObjectMapper;

import jakarta.annotation.PostConstruct;
import jakarta.annotation.PreDestroy;
import jakarta.persistence.EntityManager;
import jakarta.persistence.LockModeType;
import jakarta.persistence.PersistenceContext;

/**
 * 任务队列服务（调度与执行引擎）
 * 
 * 核心职责：
 * 1. 接收新任务并写入 task_queue 表（addTextToVideoTask / addImageToVideoTask / addStoryboardVideoTask）
 * 2. 通过内存 BlockingQueue + 消费者线程池执行任务（processTask -> 调用 RealAIService 提交外部 API）
 * 3. 任务完成/失败时更新 task_queue、业务任务表和 user_works 的状态
 * 4. 系统重启时清理未完成任务、修复 UserWork 状态不一致
 * 
 * 调用关系：
 * - 任务提交后，由本服务调用 RealAIService 提交到外部 AI API
 * - 提交成功后，创建 TaskStatus 记录，交给 TaskStatusPollingService 轮询结果
 * - 轮询到结果后，TaskStatusPollingService 回调本服务的 handleTaskCompletionByTaskId / handleTaskFailureByTaskId
 * 
 * 注意：不在类级别使用 @Transactional，因为某些方法需要禁用事务（如长时间运行的外部 API 调用）
 * 
 * @see TaskStatusPollingService
 */
@Service
public class TaskQueueService {

    private static final Logger logger = LoggerFactory.getLogger(TaskQueueService.class);

    @Autowired
    private TaskQueueRepository taskQueueRepository;
    
    @PersistenceContext
    private EntityManager entityManager;

    @Autowired
    private TextToVideoTaskRepository textToVideoTaskRepository;

    @Autowired
    private ImageToVideoTaskRepository imageToVideoTaskRepository;

    @Autowired
    private StoryboardVideoTaskRepository storyboardVideoTaskRepository;

    @Autowired
    private RealAIService realAIService;

    @Autowired
    private UserService userService;

    @Autowired
    private UserWorkService userWorkService;
    
    @Autowired
    private com.example.demo.repository.UserWorkRepository userWorkRepository;

    @Autowired
    private VideoConcatService videoConcatService;
    
    @Autowired
    private ImageGridService imageGridService;

    @Autowired
    private CosService cosService;

    @Autowired
    private TaskStatusPollingService taskStatusPollingService;

    @Autowired
    private UserErrorLogService userErrorLogService;

    @Autowired
    private SystemSettingsService systemSettingsService;

    @org.springframework.beans.factory.annotation.Value("${app.temp.dir:./temp}")
    private String tempDir;

    @org.springframework.beans.factory.annotation.Value("${app.upload.path:uploads}")
    private String uploadPath;

    private static final int MAX_TASKS_PER_USER = 3;  // 限制每个用户同时最多有3个待处理任务

    /**
     * 无界阻塞队列：用于内存中的任务处理
     * 使用 LinkedBlockingQueue 实现高性能的任务队列
     */
    private final BlockingQueue<TaskQueue> taskBlockingQueue = new LinkedBlockingQueue<>();

    /**
     * 任务处理线程池：用于并发处理队列中的任务
     * 使用固定大小的线程池，线程数量由 CONSUMER_THREAD_COUNT 决定
     */
    private ExecutorService taskProcessorExecutor;

    /**
     * 消费者线程数量
     * 配合 MAX_TASKS_PER_USER=3，理论上可支持5个用户同时处理任务（最多15个并发任务）
     * 可根据服务器性能调整此值以支持更多并发
     */
    private static final int CONSUMER_THREAD_COUNT = 20;

    /**
     * 是否正在运行
     */
    private volatile boolean running = false;

    /**
     * 初始化：启动任务处理消费者线程
     */
    @PostConstruct
    public void init() {
        logger.info("初始化任务队列服务，启动 {} 个消费者线程", CONSUMER_THREAD_COUNT);
        
        // 系统重启时，清理所有未完成的分镜图生成任务
        cleanupIncompleteStoryboardTasks();
        
        running = true;
        taskProcessorExecutor = Executors.newFixedThreadPool(CONSUMER_THREAD_COUNT, r -> {
            Thread t = new Thread(r, "task-queue-consumer");
            t.setDaemon(false);
            return t;
        });

        // 启动消费者线程
        for (int i = 0; i < CONSUMER_THREAD_COUNT; i++) {
            final int threadIndex = i;
            taskProcessorExecutor.submit(() -> {
                Thread.currentThread().setName("task-queue-consumer-" + threadIndex);
                new TaskConsumer().run();
            });
        }

        // 从数据库加载待处理任务到队列
        loadPendingTasksToQueue();
    }

    /**
     * 销毁：停止任务处理
     */
    @PreDestroy
    public void destroy() {
        logger.info("停止任务队列服务");
        running = false;
        if (taskProcessorExecutor != null) {
            taskProcessorExecutor.shutdown();
            try {
                if (!taskProcessorExecutor.awaitTermination(60, java.util.concurrent.TimeUnit.SECONDS)) {
                    taskProcessorExecutor.shutdownNow();
                }
            } catch (InterruptedException e) {
                taskProcessorExecutor.shutdownNow();
                Thread.currentThread().interrupt();
            }
        }
    }

    /**
     * 修复UserWork状态和resultUrl
     * 检查所有FAILED状态或resultUrl为空的UserWork，如果对应的业务任务已完成，则更新UserWork状态和resultUrl
     */
    /**
     * 修复 UserWork 与业务任务表之间的状态不一致
     * 
     * 覆盖场景：
     * 1. UserWork=FAILED 但业务表=COMPLETED（漏更新）
     * 2. UserWork=COMPLETED 但 resultUrl 为空
     * 3. UserWork=PROCESSING 但业务表已终态（COMPLETED/FAILED），说明卡住了
     * 
     * 此方法在系统启动时运行，同时由 TaskQueueScheduler 定期调用。
     */
    @Transactional
    public void repairUserWorkStatus() {
        try {
            logger.info("开始修复UserWork状态和resultUrl...");
            
            // 1. 修复FAILED状态的作品（业务表可能已COMPLETED）
            List<UserWork> failedWorks = userWorkRepository.findAllByFailedStatus();
            
            // 2. 修复resultUrl为空的COMPLETED作品
            List<UserWork> completedWorksWithoutUrl = userWorkRepository.findAll().stream()
                .filter(work -> work.getStatus() == UserWork.WorkStatus.COMPLETED 
                             && (work.getResultUrl() == null || work.getResultUrl().isEmpty()))
                .limit(100)
                .collect(java.util.stream.Collectors.toList());
            
            // 3. 修复卡在 PROCESSING 超过 10 分钟的作品
            LocalDateTime stuckThreshold = LocalDateTime.now().minusMinutes(10);
            List<UserWork> stuckProcessingWorks = userWorkRepository.findAll().stream()
                .filter(work -> work.getStatus() == UserWork.WorkStatus.PROCESSING
                             && work.getUpdatedAt() != null
                             && work.getUpdatedAt().isBefore(stuckThreshold))
                .limit(100)
                .collect(java.util.stream.Collectors.toList());
            
            // 合并所有需要修复的列表
            List<UserWork> allWorksToRepair = new java.util.ArrayList<>();
            allWorksToRepair.addAll(failedWorks);
            allWorksToRepair.addAll(completedWorksWithoutUrl);
            allWorksToRepair.addAll(stuckProcessingWorks);
            
            if (allWorksToRepair.isEmpty()) {
                logger.debug("没有需要修复的UserWork记录");
                return;
            }
            
            java.util.concurrent.atomic.AtomicInteger repairedCount = new java.util.concurrent.atomic.AtomicInteger(0);
            
            for (UserWork work : allWorksToRepair) {
                try {
                    boolean repaired = syncSingleWorkFromTask(work);
                    if (repaired) {
                        repairedCount.incrementAndGet();
                    }
                } catch (Exception e) {
                    logger.warn("修复单个UserWork记录失败: taskId={}", work.getTaskId(), e);
                }
            }
            
            if (repairedCount.get() > 0) {
                logger.info("UserWork状态修复完成，共检查 {} 个（FAILED:{}, resultUrl为空:{}, 卡住PROCESSING:{}），修复 {} 个", 
                    allWorksToRepair.size(), failedWorks.size(), completedWorksWithoutUrl.size(), 
                    stuckProcessingWorks.size(), repairedCount.get());
            }
        } catch (Exception e) {
            logger.error("修复UserWork状态失败", e);
        }
    }
    
    /**
     * 将单个 UserWork 的状态与对应的业务任务表同步
     * @return true 如果发生了修复
     */
    private boolean syncSingleWorkFromTask(UserWork work) {
        String taskId = work.getTaskId();
        if (taskId == null) return false;
        
        switch (work.getWorkType()) {
            case TEXT_TO_VIDEO:
                return textToVideoTaskRepository.findByTaskId(taskId).map(task -> {
                    return applyTaskStateToWork(work, 
                        task.getStatus() == TextToVideoTask.TaskStatus.COMPLETED,
                        task.getStatus() == TextToVideoTask.TaskStatus.FAILED,
                        task.getResultUrl(), task.getCompletedAt(), task.getErrorMessage());
                }).orElse(false);
                
            case IMAGE_TO_VIDEO:
                return imageToVideoTaskRepository.findByTaskId(taskId).map(task -> {
                    return applyTaskStateToWork(work,
                        task.getStatus() == ImageToVideoTask.TaskStatus.COMPLETED,
                        task.getStatus() == ImageToVideoTask.TaskStatus.FAILED,
                        task.getResultUrl(), task.getCompletedAt(), task.getErrorMessage());
                }).orElse(false);
                
            case STORYBOARD_VIDEO:
                return storyboardVideoTaskRepository.findByTaskId(taskId).map(task -> {
                    return applyTaskStateToWork(work,
                        task.getStatus() == StoryboardVideoTask.TaskStatus.COMPLETED,
                        task.getStatus() == StoryboardVideoTask.TaskStatus.FAILED,
                        task.getResultUrl(), task.getCompletedAt(), task.getErrorMessage());
                }).orElse(false);
                
            default:
                return false;
        }
    }
    
    /**
     * 根据业务任务的实际状态修复 UserWork 记录
     */
    private boolean applyTaskStateToWork(UserWork work, boolean taskCompleted, boolean taskFailed,
                                          String resultUrl, LocalDateTime completedAt, String errorMessage) {
        boolean changed = false;
        
        if (taskCompleted && work.getStatus() != UserWork.WorkStatus.COMPLETED) {
            work.setStatus(UserWork.WorkStatus.COMPLETED);
            work.setCompletedAt(completedAt);
            changed = true;
        }
        if (taskCompleted && resultUrl != null && !resultUrl.isEmpty()
                && (work.getResultUrl() == null || work.getResultUrl().isEmpty())) {
            work.setResultUrl(resultUrl);
            changed = true;
        }
        if (taskFailed && work.getStatus() == UserWork.WorkStatus.PROCESSING) {
            work.setStatus(UserWork.WorkStatus.FAILED);
            work.setCompletedAt(completedAt);
            changed = true;
        }
        
        if (changed) {
            work.setUpdatedAt(LocalDateTime.now());
            userWorkRepository.save(work);
            logger.info("修复UserWork: taskId={}, newStatus={}, hasResultUrl={}", 
                work.getTaskId(), work.getStatus(), work.getResultUrl() != null);
        }
        return changed;
    }
    
    /**
     * 从数据库加载待处理任务到内存队列
     */
    private void loadPendingTasksToQueue() {
        try {
            List<TaskQueue> pendingTasks = getPendingTasks();
            logger.info("从数据库加载 {} 个待处理任务到内存队列", pendingTasks.size());
            for (TaskQueue task : pendingTasks) {
                taskBlockingQueue.offer(task); // 非阻塞添加
            }
        } catch (Exception e) {
            logger.error("加载待处理任务到队列失败", e);
        }
    }

    /**
     * 清理未完成的任务
     * 系统重启时调用，将所有PENDING和PROCESSING状态的任务标记为失败
     * 这样可以避免队列"满了"的问题，因为重启前的任务不应该继续占用队列
     */
    @Transactional
    private void cleanupIncompleteStoryboardTasks() {
        try {
            logger.info("系统重启：开始清理未完成的任务");
            
            int totalCleanedCount = 0;
            
            // 1. 清理TaskQueue中所有PENDING和PROCESSING状态的任务
            List<TaskQueue> pendingQueues = taskQueueRepository.findByStatus(TaskQueue.QueueStatus.PENDING);
            List<TaskQueue> processingQueues = taskQueueRepository.findByStatus(TaskQueue.QueueStatus.PROCESSING);
            
            // 超时阈值：任务创建超过1小时视为超时
            LocalDateTime timeoutThreshold = LocalDateTime.now().minusHours(1);
            
            // 合并所有需要处理的队列任务
            List<TaskQueue> allQueues = new ArrayList<>();
            allQueues.addAll(pendingQueues);
            allQueues.addAll(processingQueues);
            
            for (TaskQueue taskQueue : allQueues) {
                boolean shouldRecover = false;  // 默认不恢复
                String failReason = "系统重启，任务已取消";
                
                // 只有已提交到外部API的任务才有可能恢复
                if (taskQueue.getRealTaskId() != null && !taskQueue.getRealTaskId().isEmpty()) {
                    // 超时检查：创建时间超过1小时 或 检查次数已达上限
                    boolean isTimeout = taskQueue.getCreatedAt().isBefore(timeoutThreshold);
                    boolean isMaxCheckReached = taskQueue.getCheckCount() >= taskQueue.getMaxCheckCount();
                    
                    if (isTimeout) {
                        failReason = "任务超时（创建超过1小时）";
                    } else if (isMaxCheckReached) {
                        failReason = "任务超时（检查次数已达上限）";
                    } else {
                        // 未超时，查询外部API状态（最多重试3次）
                        int maxRetries = 3;
                        int retryCount = 0;
                        boolean apiQuerySuccess = false;
                        String lastError = null;
                        
                        while (retryCount < maxRetries && !apiQuerySuccess) {
                            retryCount++;
                            try {
                                logger.info("系统重启：查询任务 {} 的外部API状态（realTaskId={}，第{}次尝试）", 
                                    taskQueue.getTaskId(), taskQueue.getRealTaskId(), retryCount);
                                
                                Map<String, Object> statusResponse = realAIService.getTaskStatus(taskQueue.getRealTaskId());
                                
                                if (statusResponse != null && statusResponse.containsKey("data")) {
                                    Object data = statusResponse.get("data");
                                    if (data instanceof Map) {
                                        @SuppressWarnings("unchecked")
                                        Map<String, Object> dataMap = (Map<String, Object>) data;
                                        String externalStatus = String.valueOf(dataMap.get("status"));
                                        
                                        apiQuerySuccess = true;  // API查询成功
                                        
                                        // 只有外部API状态为"进行中"或"已完成"才恢复
                                        if ("Processing".equalsIgnoreCase(externalStatus) || 
                                            "Pending".equalsIgnoreCase(externalStatus) ||
                                            "Running".equalsIgnoreCase(externalStatus) ||
                                            "Queued".equalsIgnoreCase(externalStatus) ||
                                            "Success".equalsIgnoreCase(externalStatus) ||
                                            "Completed".equalsIgnoreCase(externalStatus)) {
                                            shouldRecover = true;
                                            logger.info("系统重启：任务 {} 外部API状态为{}，恢复轮询", 
                                                taskQueue.getTaskId(), externalStatus);
                                        } else {
                                            failReason = "外部API状态: " + externalStatus;
                                            logger.warn("系统重启：任务 {} 外部API状态为{}，不恢复", 
                                                taskQueue.getTaskId(), externalStatus);
                                        }
                                    }
                                } else {
                                    lastError = "无法获取外部API状态";
                                    logger.warn("系统重启：查询任务 {} 外部状态返回空（第{}次）", taskQueue.getTaskId(), retryCount);
                                }
                            } catch (Exception e) {
                                lastError = e.getMessage();
                                logger.warn("系统重启：查询任务 {} 外部状态失败（第{}次）: {}", taskQueue.getTaskId(), retryCount, e.getMessage());
                            }
                            
                            // 如果还需要重试，等待1秒
                            if (!apiQuerySuccess && retryCount < maxRetries) {
                                try {
                                    Thread.sleep(1000);
                                } catch (InterruptedException ie) {
                                    Thread.currentThread().interrupt();
                                    break;
                                }
                            }
                        }
                        
                        // 如果3次查询都失败，仍然恢复任务（让后续轮询继续处理）
                        if (!apiQuerySuccess) {
                            shouldRecover = true;  // 查询失败时也恢复，让轮询服务继续处理
                            logger.warn("系统重启：任务 {} 连续{}次查询外部API失败，但仍恢复任务继续轮询（最后错误: {}）", 
                                taskQueue.getTaskId(), maxRetries, lastError);
                        }
                    }
                }
                
                if (shouldRecover) {
                    // 恢复任务状态为PROCESSING，让轮询服务继续处理
                    taskQueue.updateStatus(TaskQueue.QueueStatus.PROCESSING);
                    taskQueueRepository.save(taskQueue);
                } else {
                    // 不恢复，标记为失败
                    logger.warn("系统重启：任务 {} 不恢复，原因: {}", taskQueue.getTaskId(), failReason);
                    taskQueue.updateStatus(TaskQueue.QueueStatus.FAILED);
                    taskQueue.setErrorMessage(failReason);
                    taskQueueRepository.save(taskQueue);
                    
                    // 更新业务表和 UserWork（通过级联或回退）
                    try {
                        updateRelatedTaskStatusWithError(taskQueue.getTaskId(), taskQueue.getTaskType(), failReason);
                    } catch (Exception e) {
                        logger.warn("系统重启：更新业务表状态失败: taskId={}", taskQueue.getTaskId());
                    }
                    
                    // 返还冻结的积分
                    try {
                        // 分镜视频任务需要同时返还 _vid 和 _img 积分
                        if (taskQueue.getTaskType() == CommonTaskType.STORYBOARD_VIDEO) {
                            try {
                                userService.returnFrozenPoints(taskQueue.getTaskId() + "_vid");
                                logger.debug("系统重启：已返还分镜视频积分: taskId={}_vid", taskQueue.getTaskId());
                            } catch (Exception e) {
                                logger.debug("系统重启：分镜视频积分返还失败（可能未冻结）: taskId={}_vid", taskQueue.getTaskId());
                            }
                            try {
                                userService.returnFrozenPoints(taskQueue.getTaskId() + "_img");
                                logger.debug("系统重启：已返还分镜图积分: taskId={}_img", taskQueue.getTaskId());
                            } catch (Exception e) {
                                logger.debug("系统重启：分镜图积分返还失败（可能未冻结）: taskId={}_img", taskQueue.getTaskId());
                            }
                        } else {
                            userService.returnFrozenPoints(taskQueue.getTaskId());
                        }
                    } catch (Exception e) {
                        logger.debug("返还积分失败（可能未冻结）: taskId={}", taskQueue.getTaskId());
                    }
                    
                    totalCleanedCount++;
                }
            }
            
            // 2. 处理业务任务表中的PENDING和PROCESSING状态任务
            // 只标记超时的任务为失败，未超时的任务保持原状态（继续轮询）
            int businessTaskCleanedCount = 0;
            int businessTaskRecoveredCount = 0;
            
            // 2.1 处理文生视频任务
            List<TextToVideoTask> textToVideoTasks = textToVideoTaskRepository.findByStatus(TextToVideoTask.TaskStatus.PENDING);
            textToVideoTasks.addAll(textToVideoTaskRepository.findByStatus(TextToVideoTask.TaskStatus.PROCESSING));
            for (TextToVideoTask task : textToVideoTasks) {
                // 检查是否超时
                boolean isTimeout = task.getCreatedAt() != null && task.getCreatedAt().isBefore(timeoutThreshold);
                if (isTimeout) {
                    // 尝试通过 task_status 触发级联更新
                    boolean updated = taskStatusPollingService.markTaskFailed(task.getTaskId(), "任务超时（创建超过1小时）");
                    if (!updated) {
                        // 回退：直接更新业务表
                        task.updateStatus(TextToVideoTask.TaskStatus.FAILED);
                        task.setErrorMessage("任务超时（创建超过1小时）");
                        task.setCompletedAt(java.time.LocalDateTime.now());
                        textToVideoTaskRepository.save(task);
                        // 回退时记录错误日志
                        try {
                            userErrorLogService.logErrorAsync(
                                task.getUsername(),
                                com.example.demo.model.UserErrorLog.ErrorType.TASK_TIMEOUT,
                                "任务超时（创建超过1小时）",
                                "TaskQueueService.recoverOrCleanupStuckTasks",
                                task.getTaskId(),
                                "TEXT_TO_VIDEO"
                            );
                        } catch (Exception logEx) {
                            logger.warn("记录错误日志失败: {}", task.getTaskId());
                        }
                    }
                    businessTaskCleanedCount++;
                    logger.warn("系统重启：文生视频任务 {} 已超时，标记为失败", task.getTaskId());
                } else {
                    // 未超时任务保持原状态，继续轮询
                    businessTaskRecoveredCount++;
                    logger.info("系统重启：文生视频任务 {} 未超时，保持原状态继续执行", task.getTaskId());
                }
            }
            
            // 2.2 处理图生视频任务
            List<ImageToVideoTask> imageToVideoTasks = imageToVideoTaskRepository.findByStatus(ImageToVideoTask.TaskStatus.PENDING);
            imageToVideoTasks.addAll(imageToVideoTaskRepository.findByStatus(ImageToVideoTask.TaskStatus.PROCESSING));
            for (ImageToVideoTask task : imageToVideoTasks) {
                // 检查是否超时
                boolean isTimeout = task.getCreatedAt() != null && task.getCreatedAt().isBefore(timeoutThreshold);
                if (isTimeout) {
                    // 尝试通过 task_status 触发级联更新
                    boolean updated = taskStatusPollingService.markTaskFailed(task.getTaskId(), "任务超时（创建超过1小时）");
                    if (!updated) {
                        // 回退：直接更新业务表
                        task.updateStatus(ImageToVideoTask.TaskStatus.FAILED);
                        task.setErrorMessage("任务超时（创建超过1小时）");
                        task.setCompletedAt(java.time.LocalDateTime.now());
                        imageToVideoTaskRepository.save(task);
                        // 回退时记录错误日志
                        try {
                            userErrorLogService.logErrorAsync(
                                task.getUsername(),
                                com.example.demo.model.UserErrorLog.ErrorType.TASK_TIMEOUT,
                                "任务超时（创建超过1小时）",
                                "TaskQueueService.recoverOrCleanupStuckTasks",
                                task.getTaskId(),
                                "IMAGE_TO_VIDEO"
                            );
                        } catch (Exception logEx) {
                            logger.warn("记录错误日志失败: {}", task.getTaskId());
                        }
                    }
                    businessTaskCleanedCount++;
                    logger.warn("系统重启：图生视频任务 {} 已超时，标记为失败", task.getTaskId());
                } else {
                    // 未超时任务保持原状态，继续轮询
                    businessTaskRecoveredCount++;
                    logger.info("系统重启：图生视频任务 {} 未超时，保持原状态继续执行", task.getTaskId());
                }
            }
            
            // 2.3 处理分镜视频任务
            List<StoryboardVideoTask> storyboardTasks = storyboardVideoTaskRepository.findByStatus(StoryboardVideoTask.TaskStatus.PENDING);
            storyboardTasks.addAll(storyboardVideoTaskRepository.findByStatus(StoryboardVideoTask.TaskStatus.PROCESSING));
            for (StoryboardVideoTask task : storyboardTasks) {
                // 没有realTaskId的任务（还在生成分镜图阶段）或已超时的任务标记为失败
                boolean isTimeout = task.getCreatedAt() != null && task.getCreatedAt().isBefore(timeoutThreshold);
                boolean noRealTaskId = task.getRealTaskId() == null || task.getRealTaskId().isEmpty();
                
                if (isTimeout) {
                    // 尝试通过 task_status 触发级联更新
                    boolean updated = taskStatusPollingService.markTaskFailed(task.getTaskId(), "任务超时（创建超过1小时）");
                    if (!updated) {
                        // 回退：直接更新业务表
                        task.updateStatus(StoryboardVideoTask.TaskStatus.FAILED);
                        task.setErrorMessage("任务超时（创建超过1小时）");
                        task.setCompletedAt(java.time.LocalDateTime.now());
                        storyboardVideoTaskRepository.save(task);
                        // 回退时记录错误日志
                        try {
                            userErrorLogService.logErrorAsync(
                                task.getUsername(),
                                com.example.demo.model.UserErrorLog.ErrorType.TASK_TIMEOUT,
                                "任务超时（创建超过1小时）",
                                "TaskQueueService.recoverOrCleanupStuckTasks",
                                task.getTaskId(),
                                "STORYBOARD_VIDEO"
                            );
                        } catch (Exception logEx) {
                            logger.warn("记录错误日志失败: {}", task.getTaskId());
                        }
                    }
                    // 返还分镜任务冻结的积分（_img 和 _vid）
                    try {
                        userService.returnFrozenPoints(task.getTaskId() + "_vid");
                        logger.debug("系统重启：已返还分镜视频积分: taskId={}_vid", task.getTaskId());
                    } catch (Exception e) {
                        logger.debug("系统重启：分镜视频积分返还失败（可能未冻结）: taskId={}_vid", task.getTaskId());
                    }
                    try {
                        userService.returnFrozenPoints(task.getTaskId() + "_img");
                        logger.debug("系统重启：已返还分镜图积分: taskId={}_img", task.getTaskId());
                    } catch (Exception e) {
                        logger.debug("系统重启：分镜图积分返还失败（可能未冻结）: taskId={}_img", task.getTaskId());
                    }
                    businessTaskCleanedCount++;
                    logger.warn("系统重启：分镜视频任务 {} 已超时，标记为失败", task.getTaskId());
                } else if (noRealTaskId) {
                    // 尝试通过 task_status 触发级联更新
                    boolean updated = taskStatusPollingService.markTaskFailed(task.getTaskId(), "系统重启，分镜图生成任务已取消");
                    if (!updated) {
                        // 回退：直接更新业务表
                        task.updateStatus(StoryboardVideoTask.TaskStatus.FAILED);
                        task.setErrorMessage("系统重启，分镜图生成任务已取消");
                        task.setCompletedAt(java.time.LocalDateTime.now());
                        storyboardVideoTaskRepository.save(task);
                        // 回退时记录错误日志
                        try {
                            userErrorLogService.logErrorAsync(
                                task.getUsername(),
                                com.example.demo.model.UserErrorLog.ErrorType.TASK_PROCESSING_ERROR,
                                "系统重启，分镜图生成任务已取消",
                                "TaskQueueService.recoverOrCleanupStuckTasks",
                                task.getTaskId(),
                                "STORYBOARD_VIDEO"
                            );
                        } catch (Exception logEx) {
                            logger.warn("记录错误日志失败: {}", task.getTaskId());
                        }
                    }
                    // 返还分镜图冻结的积分
                    try {
                        userService.returnFrozenPoints(task.getTaskId() + "_img");
                        logger.debug("系统重启：已返还分镜图积分: taskId={}_img", task.getTaskId());
                    } catch (Exception e) {
                        logger.debug("系统重启：分镜图积分返还失败（可能未冻结）: taskId={}_img", task.getTaskId());
                    }
                    // 修复：同时尝试返还视频积分（用户可能已点击"生成视频"但任务未提交到API）
                    try {
                        userService.returnFrozenPoints(task.getTaskId() + "_vid");
                        logger.debug("系统重启：已返还分镜视频积分: taskId={}_vid", task.getTaskId());
                    } catch (Exception e) {
                        logger.debug("系统重启：分镜视频积分返还失败（可能未冻结）: taskId={}_vid", task.getTaskId());
                    }
                    businessTaskCleanedCount++;
                    logger.warn("系统重启：分镜视频任务 {} 无外部任务ID，标记为失败", task.getTaskId());
                } else {
                    // 已提交到外部API且未超时的任务，保持原状态继续轮询
                    businessTaskRecoveredCount++;
                    logger.info("系统重启：分镜视频任务 {} 未超时，保持原状态继续执行", task.getTaskId());
                }
            }
            
            // 3. 修复UserWork状态
            // 检查所有FAILED状态的UserWork，如果对应的业务任务已完成，则更新UserWork状态
            repairUserWorkStatus();
            
            if (totalCleanedCount > 0 || businessTaskCleanedCount > 0 || businessTaskRecoveredCount > 0) {
                logger.info("系统重启：清理了 {} 个队列任务，{} 个业务任务；恢复了 {} 个业务任务继续执行", 
                    totalCleanedCount, businessTaskCleanedCount, businessTaskRecoveredCount);
            } else {
                logger.info("系统重启：没有需要清理的未完成任务");
            }
            
        } catch (Exception e) {
            logger.error("清理未完成任务失败", e);
        }
    }

    /**
     * 任务消费者：从阻塞队列中取任务并处理
     */
    private class TaskConsumer implements Runnable {
        @Override
        public void run() {
            logger.info("任务消费者线程启动: {}", Thread.currentThread().getName());
            while (running || !taskBlockingQueue.isEmpty()) {
                try {
                    // 阻塞等待任务，最多等待1秒
                    TaskQueue task = taskBlockingQueue.poll(1, java.util.concurrent.TimeUnit.SECONDS);
                    if (task != null) {
                        try {
                            processTask(task);
                        } catch (Exception e) {
                            logger.error("处理任务失败: {}", task.getTaskId(), e);
                            // markTaskAsFailed 内部会返还积分并删除队列记录
                            markTaskAsFailed(task.getTaskId(), "处理失败: " + e.getMessage());
                        }
                    }
                } catch (InterruptedException e) {
                    Thread.currentThread().interrupt();
                    logger.info("任务消费者线程被中断: {}", Thread.currentThread().getName());
                    break;
                } catch (Exception e) {
                    logger.error("任务消费者线程异常", e);
                }
            }
            logger.info("任务消费者线程结束: {}", Thread.currentThread().getName());
        }
    }

    /**
     * 添加文生视频任务到队列
     */
    @Transactional
    public TaskQueue addTextToVideoTask(String username, String taskId) {
        return addTaskToQueue(username, taskId, CommonTaskType.TEXT_TO_VIDEO);
    }

    /**
     * 添加图生视频任务到队列
     */
    @Transactional
    public TaskQueue addImageToVideoTask(String username, String taskId) {
        return addTaskToQueue(username, taskId, CommonTaskType.IMAGE_TO_VIDEO);
    }

    /**
     * 添加分镜视频任务到队列
     */
    @Transactional
    public TaskQueue addStoryboardVideoTask(String username, String taskId) {
        return addTaskToQueue(username, taskId, CommonTaskType.STORYBOARD_VIDEO);
    }

    /**
     * 添加任务到队列
     * 注意：私有方法上的 @Transactional 不生效，事务由调用者（公共方法）管理
     */
    private TaskQueue addTaskToQueue(String username, String taskId, CommonTaskType taskType) {
        // 检查用户是否已有待处理任务
        long pendingCount = taskQueueRepository.countPendingTasksByUsername(username);
        if (pendingCount >= MAX_TASKS_PER_USER) {
            throw new RuntimeException("您当前已有" + MAX_TASKS_PER_USER + "个任务正在进行中，请等待任务完成后再创建新任务");
        }

        // 检查任务是否已存在
        Optional<TaskQueue> existingTask = taskQueueRepository.findByTaskId(taskId);
        if (existingTask.isPresent()) {
            TaskQueue oldTask = existingTask.get();
            TaskQueue.QueueStatus oldStatus = oldTask.getStatus();
            
            // COMPLETED/FAILED/CANCELLED/TIMEOUT: 直接删除旧记录，允许重新创建
            if (oldStatus == TaskQueue.QueueStatus.FAILED || 
                oldStatus == TaskQueue.QueueStatus.CANCELLED ||
                oldStatus == TaskQueue.QueueStatus.COMPLETED ||
                oldStatus == TaskQueue.QueueStatus.TIMEOUT) {
                logger.info("任务 {} 之前状态为 {}，删除旧记录允许重新创建", taskId, oldStatus);
                taskQueueRepository.delete(oldTask);
                taskQueueRepository.flush();
            } 
            // PROCESSING: 检查是否已超时（5分钟），超时才删除
            else if (oldStatus == TaskQueue.QueueStatus.PROCESSING) {
                java.time.LocalDateTime fiveMinutesAgo = java.time.LocalDateTime.now().minusMinutes(5);
                if (oldTask.getUpdatedAt().isBefore(fiveMinutesAgo)) {
                    logger.info("任务 {} 处理超时（超过5分钟），删除旧记录允许重新创建", taskId);
                    taskQueueRepository.delete(oldTask);
                    taskQueueRepository.flush();
                } else {
                    throw new RuntimeException("任务 " + taskId + " 正在处理中，请稍后再试");
                }
            }
            // PENDING: 任务正在排队，不允许重复创建
            else if (oldStatus == TaskQueue.QueueStatus.PENDING) {
                throw new RuntimeException("任务 " + taskId + " 正在排队中，请等待处理");
            }
        }

        // 计算任务所需积分 - 降低积分要求
        Integer requiredPoints = calculateRequiredPoints(taskType);
        
        // 冻结积分
        // 注意：分镜视频任务的积分已在 StoryboardVideoService 中使用 _img 和 _vid 后缀冻结
        // 这里跳过分镜视频任务的积分冻结，避免重复冻结
        if (taskType != CommonTaskType.STORYBOARD_VIDEO) {
            CommonTaskType freezeTaskType = convertTaskType(taskType);
            
            userService.freezePoints(username, taskId, freezeTaskType, requiredPoints, 
                "任务提交冻结积分 - " + taskType.getDescription());
        } else {
            logger.info("分镜视频任务 {} 跳过积分冻结（已在业务层冻结）", taskId);
        }

        // 创建新的队列任务
        TaskQueue taskQueue = new TaskQueue(username, taskId, taskType);
        taskQueue = taskQueueRepository.save(taskQueue);

        // 同时创建 task_status 记录
        try {
            CommonTaskType statusTaskType = convertToTaskStatusType(taskType);
            taskStatusPollingService.createTaskStatus(taskId, username, statusTaskType, null);
            logger.info("任务 {} 已同时创建到 task_queue 和 task_status 表", taskId);
        } catch (Exception e) {
            logger.error("创建 task_status 记录失败: taskId={}", taskId, e);
            // 不影响主流程，继续执行
        }

        // 注册事务提交后的回调，确保事务提交后才将任务加入内存队列
        // 这样可以避免消费者线程在事务提交前就开始处理任务，导致找不到数据的问题
        final TaskQueue finalTaskQueue = taskQueue;
        org.springframework.transaction.support.TransactionSynchronizationManager.registerSynchronization(
            new org.springframework.transaction.support.TransactionSynchronization() {
                @Override
                public void afterCommit() {
                    // 添加到内存阻塞队列（非阻塞，如果队列满会立即返回false，但无界队列不会满）
                    taskBlockingQueue.offer(finalTaskQueue);
                    logger.info("任务 {} 已添加到内存队列，用户: {}, 类型: {}, 冻结积分: {}, 内存队列大小: {}", 
                        finalTaskQueue.getTaskId(), username, taskType.getDescription(), requiredPoints, taskBlockingQueue.size());
                }
            }
        );
        
        logger.info("任务 {} 已保存到数据库，用户: {}, 类型: {}, 冻结积分: {}", 
                taskId, username, taskType.getDescription(), requiredPoints);
        return taskQueue;
    }

    /**
     * 计算任务所需积分 - 统一30积分
     */
    private Integer calculateRequiredPoints(CommonTaskType taskType) {
        com.example.demo.model.SystemSettings settings = systemSettingsService.getOrCreate();
        switch (taskType) {
            case TEXT_TO_VIDEO:
                return settings.getTextToVideoPoints();
            case IMAGE_TO_VIDEO:
                return settings.getImageToVideoPoints();
            case STORYBOARD_VIDEO:
                return settings.getStoryboardVideoPoints();
            case STORYBOARD_IMAGE:
                return settings.getStoryboardImagePoints();
            default:
                throw new IllegalArgumentException("不支持的任务类型: " + taskType);
        }
    }

    /**
     * 转换任务类型
     */
    private CommonTaskType convertTaskType(CommonTaskType taskType) {
        // 直接返回，因为已经统一使用 CommonTaskType
        return taskType;
    }

    /**
     * 转换 CommonTaskType 到 CommonTaskType（已统一，直接返回）
     */
    private CommonTaskType convertToTaskStatusType(CommonTaskType taskType) {
        // 直接返回，因为已经统一使用 CommonTaskType
        return taskType;
    }

    /**
     * 处理队列中的待处理任务
     * 注意：此方法现在主要用于从数据库加载任务到内存队列
     * 实际的任务处理由消费者线程从阻塞队列中取任务并处理
     */
    @Transactional(propagation = Propagation.NOT_SUPPORTED)
    public int processPendingTasks() {
        // 从数据库加载待处理任务到内存队列
        List<TaskQueue> pendingTasks = getPendingTasks();
        int addedCount = 0;
        for (TaskQueue taskQueue : pendingTasks) {
            // 使用 offer 非阻塞添加，避免重复添加
            if (taskBlockingQueue.offer(taskQueue)) {
                addedCount++;
            }
        }
        return addedCount;
    }

    /**
     * 获取内存队列的当前大小
     */
    public int getQueueSize() {
        return taskBlockingQueue.size();
    }

    /**
     * 获取待处理任务列表（只读事务，快速完成）
     * 使用 TransactionTemplate 手动管理事务，避免在 NOT_SUPPORTED 传播下的事务问题
     */
    public List<TaskQueue> getPendingTasks() {
        return readOnlyTransactionTemplate.execute(status -> {
            return taskQueueRepository.findAllPendingTasks();
        });
    }

    /**
     * 标记任务为失败（单独的事务方法）
     * 会返还冻结的积分并删除队列记录
     */
    @Transactional
    public void markTaskAsFailed(String taskId, String errorMessage) {
        Optional<TaskQueue> taskOpt = taskQueueRepository.findByTaskId(taskId);
        if (taskOpt.isPresent()) {
            TaskQueue taskQueue = taskOpt.get();
            taskQueue.updateStatus(TaskQueue.QueueStatus.FAILED);
            taskQueue.setErrorMessage(errorMessage);
            taskQueueRepository.save(taskQueue);
            logger.info("任务已标记为失败: taskId={}, error={}", taskId, errorMessage);
            
            // 更新关联的业务任务状态（ImageToVideoTask/TextToVideoTask等）
            try {
                updateRelatedTaskStatusWithError(taskId, taskQueue.getTaskType(), errorMessage);
                logger.info("已更新关联任务状态为失败: taskId={}", taskId);
            } catch (Exception e) {
                logger.error("更新关联任务状态失败: taskId={}, error={}", taskId, e.getMessage());
            }
            
            // 返还冻结的积分
            try {
                // 分镜视频使用 taskId + "_vid" 作为积分冻结ID
                // 分镜图使用 taskId + "_img" 作为积分冻结ID
                if (taskQueue.getTaskType() == CommonTaskType.STORYBOARD_VIDEO) {
                    // 尝试返还视频积分
                    try {
                        userService.returnFrozenPoints(taskId + "_vid");
                        logger.info("已返还分镜视频积分: taskId={}_vid", taskId);
                    } catch (Exception e) {
                        logger.debug("分镜视频积分返还失败（可能未冻结）: taskId={}_vid", taskId);
                    }
                    // 同时尝试返还分镜图积分（如果分镜图阶段失败）
                    try {
                        userService.returnFrozenPoints(taskId + "_img");
                        logger.info("已返还分镜图积分: taskId={}_img", taskId);
                    } catch (Exception e) {
                        logger.debug("分镜图积分返还失败（可能未冻结或已返还）: taskId={}_img", taskId);
                    }
                } else {
                    userService.returnFrozenPoints(taskId);
                }
                logger.info("已返还冻结积分: taskId={}", taskId);
            } catch (Exception e) {
                logger.warn("返还冻结积分失败（可能积分记录不存在）: taskId={}, error={}", taskId, e.getMessage());
            }
            
            // 删除队列记录（失败任务不再保留在队列中）
            try {
                taskQueueRepository.delete(taskQueue);
                logger.info("已删除失败任务队列记录: taskId={}", taskId);
            } catch (Exception e) {
                logger.error("删除失败任务队列记录失败: taskId={}, error={}", taskId, e.getMessage());
            }
        }
    }

    /**
     * 处理单个任务
     * 注意：此方法明确禁用事务，因为长时间运行的外部API调用会占用数据库连接
     */
    @Transactional(propagation = Propagation.NOT_SUPPORTED)
    private void processTask(TaskQueue taskQueue) {
        // 更新状态为处理中（使用单独的事务方法）
        // 如果状态更新失败（任务已被其他线程处理），直接返回
        if (!updateTaskStatusToProcessing(taskQueue.getTaskId())) {
            return;
        }

        try {
            // 根据任务类型调用相应的API（长时间运行，不在事务中）
            Map<String, Object> apiResponse;
            
            if (taskQueue.getTaskType() == CommonTaskType.TEXT_TO_VIDEO) {
                apiResponse = processTextToVideoTask(taskQueue);
            } else if (taskQueue.getTaskType() == CommonTaskType.IMAGE_TO_VIDEO) {
                apiResponse = processImageToVideoTask(taskQueue);
            } else if (taskQueue.getTaskType() == CommonTaskType.STORYBOARD_VIDEO) {
                // 分镜视频任务由 StoryboardVideoService 的备份处理来执行
                // 队列消费者跳过，避免并发问题
                logger.info("分镜视频任务 {} 由备份处理执行，队列消费者跳过", taskQueue.getTaskId());
                return;
            } else {
                throw new RuntimeException("不支持的任务类型: " + taskQueue.getTaskType());
            }

            // 提取真实任务ID
            String realTaskId = extractRealTaskId(apiResponse);
            if (realTaskId != null) {
                // 保存真实任务ID（使用单独的事务方法）
                saveRealTaskId(taskQueue.getTaskId(), realTaskId);
            } else {
                logger.error("无法提取任务ID: taskId={}", taskQueue.getTaskId());
                throw new RuntimeException("API未返回有效的任务ID");
            }

        } catch (Exception e) {
            logger.error("提交任务到外部API失败: taskId={}", taskQueue.getTaskId(), e);
            // 更新任务失败状态（使用单独的事务方法）
            markTaskAsFailed(taskQueue.getTaskId(), "API提交失败: " + e.getMessage());
        }
    }

    /**
     * 更新任务状态为处理中（单独的事务方法）
     * 使用悲观锁（SELECT FOR UPDATE）机制，确保同一任务不会被重复处理
     * 使用 TransactionTemplate 手动管理事务，确保悲观锁查询在事务中执行
     */
    @Autowired
    @org.springframework.beans.factory.annotation.Qualifier("asyncTransactionTemplate")
    private org.springframework.transaction.support.TransactionTemplate transactionTemplate;
    
    @Autowired
    @org.springframework.beans.factory.annotation.Qualifier("readOnlyTransactionTemplate")
    private org.springframework.transaction.support.TransactionTemplate readOnlyTransactionTemplate;
    
    private final ObjectMapper objectMapper = new ObjectMapper();
    
    // 跟踪正在查询的任务，防止重复查询
    private final Set<String> checkingTasks = ConcurrentHashMap.newKeySet();
    
    public boolean updateTaskStatusToProcessing(String taskId) {
        // 使用 TransactionTemplate 手动管理事务，确保悲观锁查询在事务中执行
        Boolean result = transactionTemplate.execute(status -> {
            try {
                // 使用 Repository 的悲观锁方法查找任务
                TaskQueue taskQueue = taskQueueRepository.findByTaskIdWithLock(taskId)
                    .orElseThrow(() -> new jakarta.persistence.NoResultException("任务不存在: " + taskId));
                
                // 检查任务是否已经有 realTaskId，如果有，说明已经提交过了，不应该再次处理
                if (taskQueue.getRealTaskId() != null && !taskQueue.getRealTaskId().isEmpty()) {
                    logger.warn("任务 {} 已经有 realTaskId（{}），说明已经提交过了，跳过处理", 
                        taskId, taskQueue.getRealTaskId());
                    return Boolean.FALSE;
                }
                
                // 检查任务状态，如果已经不是PENDING，说明已经被其他线程处理了
                if (taskQueue.getStatus() != TaskQueue.QueueStatus.PENDING) {
                    logger.warn("任务 {} 状态已不是PENDING（当前状态: {}），跳过处理，可能已被其他线程处理", 
                        taskId, taskQueue.getStatus());
                    return Boolean.FALSE;
                }
                
                // 更新状态为处理中
                taskQueue.updateStatus(TaskQueue.QueueStatus.PROCESSING);
                taskQueueRepository.save(taskQueue);
                return Boolean.TRUE;
            } catch (jakarta.persistence.NoResultException e) {
                logger.warn("任务 {} 不存在，无法更新状态", taskId);
                return Boolean.FALSE;
            } catch (Exception e) {
                logger.error("更新任务状态失败: taskId={}", taskId, e);
                throw new RuntimeException("更新任务状态失败: " + e.getMessage(), e);
            }
        });
        return result != null && result;
    }

    /**
     * 保存真实任务ID（单独的事务方法）
     * 确保任务状态保持为 PROCESSING，不会重置为 PENDING
     */
    @Transactional
    public void saveRealTaskId(String taskId, String realTaskId) {
        Optional<TaskQueue> taskOpt = taskQueueRepository.findByTaskId(taskId);
        if (taskOpt.isPresent()) {
            TaskQueue taskQueue = taskOpt.get();
            // 如果状态不是 PROCESSING，说明可能有问题，记录警告
            if (taskQueue.getStatus() != TaskQueue.QueueStatus.PROCESSING) {
                logger.warn("保存 realTaskId 时，任务 {} 状态不是 PROCESSING（当前状态: {}），保持原状态", 
                    taskId, taskQueue.getStatus());
            }
            taskQueue.setRealTaskId(realTaskId);
            taskQueueRepository.save(taskQueue);
            
            // 同时更新 TaskStatus 表的 externalTaskId（轮询服务使用此字段）
            try {
                TaskStatus taskStatus = taskStatusPollingService.getTaskStatus(taskId);
                if (taskStatus != null) {
                    taskStatus.setExternalTaskId(realTaskId);
                    taskStatusPollingService.saveOrUpdateTaskStatus(taskStatus);
                    logger.info("已更新 TaskStatus 的 externalTaskId: taskId={}, externalTaskId={}", taskId, realTaskId);
                }
            } catch (Exception e) {
                logger.warn("更新 TaskStatus 的 externalTaskId 失败: taskId={}", taskId, e);
            }
            
            // TODO: 为分镜视频任务创建或更新 TaskStatus（功能待实现）
            // 区分图生视频和分镜视频任务的状态码
            /*
            if (taskQueue.getTaskType() == CommonTaskType.STORYBOARD_VIDEO) {
                try {
                    // 检查是否已存在 TaskStatus
                    TaskStatus existingTaskStatus = taskStatusPollingService.getTaskStatus(taskId);
                    if (existingTaskStatus != null) {
                        // 更新现有的 TaskStatus
                        existingTaskStatus.setExternalTaskId(realTaskId);
                        existingTaskStatus.setStatus(TaskStatus.Status.PROCESSING);
                        existingTaskStatus.setProgress(50); // 分镜图已完成，视频生成中
                        taskStatusPollingService.saveOrUpdateTaskStatus(existingTaskStatus);
                        logger.info("已更新分镜视频任务的 TaskStatus: taskId={}, externalTaskId={}, type=STORYBOARD_VIDEO", 
                            taskId, realTaskId);
                    } else {
                        // 创建新的 TaskStatus（分镜视频类型）
                        TaskStatus taskStatus = taskStatusPollingService.createTaskStatus(
                            taskId, 
                            taskQueue.getUsername(), 
                            CommonTaskType.STORYBOARD_VIDEO,  // 使用分镜视频类型，而不是图生视频类型
                            realTaskId
                        );
                        taskStatus.setProgress(50); // 分镜图已完成，视频生成中
                        taskStatusPollingService.saveOrUpdateTaskStatus(taskStatus);
                        logger.info("已创建分镜视频任务的 TaskStatus: taskId={}, externalTaskId={}, type=STORYBOARD_VIDEO", 
                            taskId, realTaskId);
                    }
                } catch (Exception e) {
                    logger.error("为分镜视频任务创建/更新 TaskStatus 失败: taskId={}", taskId, e);
                    // 不抛出异常，避免影响主流程
                }
            }
            */
        }
    }
    
    /**
     * 增加检查次数（单独的事务方法，快速完成）
     * 注意：使用 TransactionTemplate 而非 @Transactional，因为私有方法上的注解不生效
     */
    private void incrementCheckCountWithTransaction(String taskId) {
        transactionTemplate.execute(status -> {
            Optional<TaskQueue> taskOpt = taskQueueRepository.findByTaskId(taskId);
            if (taskOpt.isPresent()) {
                TaskQueue taskQueue = taskOpt.get();
                taskQueue.incrementCheckCount();
                taskQueueRepository.save(taskQueue);
            }
            return null;
        });
    }

    /**
     * 处理文生视频任务
     * 使用只读事务快速查询任务信息
     */
    private Map<String, Object> processTextToVideoTask(TaskQueue taskQueue) {
        // 使用只读事务快速查询任务信息
        TextToVideoTask task = getTextToVideoTaskWithTransaction(taskQueue.getTaskId());
        
        return realAIService.submitTextToVideoTask(
            task.getPrompt(),
            task.getAspectRatio(),
            String.valueOf(task.getDuration()),
            task.isHdMode()
        );
    }

    /**
     * 使用只读事务获取文生视频任务（快速完成，避免连接泄漏）
     * 注意：使用 TransactionTemplate 而非 @Transactional，因为私有方法上的注解不生效
     */
    private TextToVideoTask getTextToVideoTaskWithTransaction(String taskId) {
        return readOnlyTransactionTemplate.execute(status -> {
            Optional<TextToVideoTask> taskOpt = textToVideoTaskRepository.findByTaskId(taskId);
            if (!taskOpt.isPresent()) {
                throw new RuntimeException("找不到文生视频任务: " + taskId);
            }
            return taskOpt.get();
        });
    }

    /**
     * 处理图生视频任务
     * 使用只读事务快速查询任务信息
     */
    private Map<String, Object> processImageToVideoTask(TaskQueue taskQueue) {
        // 使用只读事务快速查询任务信息
        ImageToVideoTask task = getImageToVideoTaskWithTransaction(taskQueue.getTaskId());
        
        // 从文件系统读取图片并转换为Base64
        String imageBase64 = convertImageFileToBase64(task.getFirstFrameUrl());
        
        return realAIService.submitImageToVideoTask(
            task.getPrompt(),
            imageBase64,
            task.getAspectRatio(),
            task.getDuration().toString(),
            Boolean.TRUE.equals(task.getHdMode())
        );
    }
    
    /**
     * 使用只读事务获取图生视频任务（快速完成，避免连接泄漏）
     * 注意：使用 TransactionTemplate 而非 @Transactional，因为私有方法上的注解不生效
     */
    private ImageToVideoTask getImageToVideoTaskWithTransaction(String taskId) {
        return readOnlyTransactionTemplate.execute(status -> {
            Optional<ImageToVideoTask> taskOpt = imageToVideoTaskRepository.findByTaskId(taskId);
            if (!taskOpt.isPresent()) {
                throw new RuntimeException("找不到图生视频任务: " + taskId);
            }
            return taskOpt.get();
        });
    }

    /**
     * 处理分镜视频任务
     * 将6张分镜图拼接成一张图片，然后调用图生视频接口生成视频
     * 使用只读事务快速查询任务信息
     */
    private Map<String, Object> processStoryboardVideoTask(TaskQueue taskQueue) {
        // 使用只读事务快速查询任务信息
        StoryboardVideoTask task = getStoryboardVideoTaskWithTransaction(taskQueue.getTaskId());
        
        // 调试日志：检查从数据库加载的提示词字段
        logger.info("【调试】任务提示词字段: taskId={}, prompt={}, shotList={}, imagePrompt={}, videoPrompt={}", 
            task.getTaskId(),
            task.getPrompt() != null ? task.getPrompt().substring(0, Math.min(50, task.getPrompt().length())) + "..." : "null",
            task.getShotList() != null ? "长度:" + task.getShotList().length() : "null",
            task.getImagePrompt() != null ? "长度:" + task.getImagePrompt().length() : "null",
            task.getVideoPrompt() != null ? "长度:" + task.getVideoPrompt().length() : "null");
        
        // 获取6张分镜图片
        List<String> images = null;
        String storyboardImagesJson = task.getStoryboardImages();
        
        if (storyboardImagesJson != null && !storyboardImagesJson.isEmpty()) {
            try {
                // 解析JSON数组（每张图片都是Base64格式，带data URI前缀）
                List<String> parsedImages = objectMapper.readValue(
                    storyboardImagesJson, 
                    objectMapper.getTypeFactory().constructCollectionType(List.class, String.class)
                );
                if (parsedImages != null && !parsedImages.isEmpty()) {
                    images = parsedImages;
                    logger.info("获取到分镜图片，数量: {}", images.size());
                }
            } catch (Exception e) {
                logger.warn("解析分镜图片JSON失败: {}", e.getMessage());
            }
        }
        
        // 如果没有单独的分镜图片，使用网格图（向后兼容）
        if (images == null || images.isEmpty()) {
            String imageBase64 = task.getResultUrl();
            if (imageBase64 == null || imageBase64.isEmpty()) {
                throw new RuntimeException("分镜图未生成，无法生成视频");
            }
            // 如果只有网格图，直接使用（已经是拼接后的图片）
            logger.info("使用网格图生成视频（向后兼容）");
            
            // 组合视频提示词：shotList + videoPrompt
            StringBuilder videoPromptBuilder = new StringBuilder();
            if (task.getShotList() != null && !task.getShotList().trim().isEmpty()) {
                videoPromptBuilder.append(task.getShotList().trim());
            }
            if (task.getVideoPrompt() != null && !task.getVideoPrompt().trim().isEmpty()) {
                if (videoPromptBuilder.length() > 0) {
                    videoPromptBuilder.append("\n\n");
                }
                videoPromptBuilder.append(task.getVideoPrompt().trim());
            }
            String videoPromptForApi = videoPromptBuilder.length() > 0 ? videoPromptBuilder.toString() : task.getPrompt();
            
            // 直接使用网格图调用图生视频接口
            String gridAspectRatio = task.getAspectRatio();
            String gridDuration = task.getDuration() != null ? task.getDuration().toString() : "10";
            boolean gridHdMode = task.isHdMode();
            logger.info("提交视频生成请求(网格图): aspectRatio={}, duration={}, hdMode={}",
                gridAspectRatio, gridDuration, gridHdMode);
            Map<String, Object> result = realAIService.submitImageToVideoTask(
                videoPromptForApi,
                imageBase64,
                gridAspectRatio,
                gridDuration,
                gridHdMode
            );
            
            if (result != null && result.containsKey("task_id")) {
                String videoTaskId = result.get("task_id").toString();
                // 保存视频任务ID到数据库
                saveVideoTaskId(taskQueue.getTaskId(), videoTaskId);
                return result;
            } else {
                throw new RuntimeException("图生视频任务提交失败，未返回task_id");
            }
        }
        
        // 获取分镜图（取第一张）
        String storyboardImage = images.get(0);
        
        // 解析视频阶段用户上传的参考图（优先使用 videoReferenceImages 字段）
        List<String> userUploadedImages = new ArrayList<>();
        String videoRefImagesJson = task.getVideoReferenceImages();
        if (videoRefImagesJson != null && !videoRefImagesJson.isEmpty()) {
            try {
                List<String> parsedUploads = objectMapper.readValue(videoRefImagesJson, 
                    objectMapper.getTypeFactory().constructCollectionType(List.class, String.class));
                for (String img : parsedUploads) {
                    if (img != null && !img.isEmpty() && !"null".equals(img)) {
                        userUploadedImages.add(img);
                    }
                }
                logger.info("解析视频阶段参考图，有效数量: {}", userUploadedImages.size());
            } catch (Exception e) {
                logger.warn("解析视频阶段参考图失败: {}", e.getMessage());
            }
        }
        // 注意：不再使用 uploadedImages（分镜图阶段的参考图），只使用 videoReferenceImages
        
        // 生成视频时进行拼图处理（分镜图 + 视频阶段用户上传的参考图）
        String imageForVideo;
        if (!userUploadedImages.isEmpty()) {
            // 有用户上传图片，创建复合布局
            logger.info("创建复合布局（分镜图 + {}张视频参考图）: 目标比例={}", userUploadedImages.size(), task.getAspectRatio());
            try {
                imageForVideo = imageGridService.createStoryboardLayout(storyboardImage, userUploadedImages, task.getAspectRatio());
            } catch (Exception e) {
                logger.error("创建复合布局失败: {}", e.getMessage(), e);
                throw new RuntimeException("创建复合布局失败: " + e.getMessage(), e);
            }
        } else {
            // 无用户上传图片，创建简单布局（分镜图适配目标比例）
            logger.info("创建简单布局（无视频参考图）: 目标比例={}", task.getAspectRatio());
            try {
                imageForVideo = imageGridService.createStoryboardLayout(storyboardImage, null, task.getAspectRatio());
            } catch (Exception e) {
                logger.error("创建简单布局失败: {}", e.getMessage(), e);
                throw new RuntimeException("创建简单布局失败: " + e.getMessage(), e);
            }
        }
        
        // 组合视频提示词：shotList + videoPrompt 组合成视频生成提示词
        StringBuilder videoPromptBuilder = new StringBuilder();
        
        // 添加 shotList（分镜描述）
        if (task.getShotList() != null && !task.getShotList().trim().isEmpty()) {
            videoPromptBuilder.append(task.getShotList().trim());
            logger.info("添加 shotList 到视频提示词，长度: {}", task.getShotList().length());
        }
        
        // 添加 videoPrompt
        if (task.getVideoPrompt() != null && !task.getVideoPrompt().trim().isEmpty()) {
            if (videoPromptBuilder.length() > 0) {
                videoPromptBuilder.append("\n\n");
            }
            videoPromptBuilder.append(task.getVideoPrompt().trim());
            logger.info("添加 videoPrompt 到视频提示词，长度: {}", task.getVideoPrompt().length());
        }
        
        // 如果都没有，使用原始提示词
        String videoPromptForApi;
        if (videoPromptBuilder.length() > 0) {
            videoPromptForApi = videoPromptBuilder.toString();
            logger.info("组合后的视频提示词长度: {}", videoPromptForApi.length());
            // 调试：显示实际使用的提示词前200字符
            logger.info("【调试】实际使用的视频提示词: {}", 
                videoPromptForApi.length() > 200 ? videoPromptForApi.substring(0, 200) + "..." : videoPromptForApi);
        } else {
            videoPromptForApi = task.getPrompt();
            logger.info("使用原始提示词生成视频");
            logger.warn("【警告】shotList 和 videoPrompt 都为空，使用用户原始提示词: {}", 
                videoPromptForApi != null ? videoPromptForApi.substring(0, Math.min(100, videoPromptForApi.length())) : "null");
        }
        
        // 使用拼好的图片调用图生视频接口
        String finalAspectRatio = task.getAspectRatio();
        String finalDuration = task.getDuration() != null ? task.getDuration().toString() : "10";
        boolean finalHdMode = task.isHdMode();
        logger.info("提交视频生成请求: aspectRatio={}, duration={}, hdMode={}, 提示词长度={}",
            finalAspectRatio, finalDuration, finalHdMode, videoPromptForApi.length());
        Map<String, Object> result = realAIService.submitImageToVideoTask(
            videoPromptForApi,
            imageForVideo,
            task.getAspectRatio(),
            task.getDuration() != null ? task.getDuration().toString() : "10",
            task.isHdMode()
        );
        
        if (result != null && result.containsKey("task_id")) {
            String videoTaskId = result.get("task_id").toString();
            // 保存视频任务ID到数据库
            saveVideoTaskId(taskQueue.getTaskId(), videoTaskId);
            logger.info("分镜视频任务提交成功，task_id: {}", videoTaskId);
            return result;
        } else {
            throw new RuntimeException("图生视频任务提交失败，未返回task_id");
        }
    }
    
    /**
     * 保存视频任务ID到数据库
     */
    private void saveVideoTaskId(String taskId, String videoTaskId) {
        try {
            transactionTemplate.executeWithoutResult(status -> {
                // 1. 更新 StoryboardVideoTask
                StoryboardVideoTask currentTask = storyboardVideoTaskRepository.findByTaskId(taskId)
                    .orElseThrow(() -> new RuntimeException("任务不存在: " + taskId));
                // 保存为单个任务ID（不再使用videoTaskIds数组）
                currentTask.setRealTaskId(videoTaskId);
                storyboardVideoTaskRepository.save(currentTask);

                // 2. 同时更新 TaskQueue，这一步非常关键，否则轮询服务找不到realTaskId
                Optional<TaskQueue> taskQueueOpt = taskQueueRepository.findByTaskId(taskId);
                if (taskQueueOpt.isPresent()) {
                    TaskQueue taskQueue = taskQueueOpt.get();
                    taskQueue.setRealTaskId(videoTaskId);
                    taskQueueRepository.save(taskQueue);
                    logger.info("已更新任务队列的 realTaskId: taskId={}, realTaskId={}", taskId, videoTaskId);
                } else {
                    logger.warn("找不到对应的任务队列记录: {}", taskId);
                }
                
                // 3. 同时更新 TaskStatus 表的 externalTaskId（轮询服务使用此字段）
                try {
                    TaskStatus taskStatus = taskStatusPollingService.getTaskStatus(taskId);
                    if (taskStatus != null) {
                        taskStatus.setExternalTaskId(videoTaskId);
                        taskStatus.setProgress(50); // 分镜图已完成，视频生成中
                        taskStatusPollingService.saveOrUpdateTaskStatus(taskStatus);
                        logger.info("已更新分镜视频任务的 TaskStatus externalTaskId: taskId={}, externalTaskId={}", taskId, videoTaskId);
                    }
                } catch (Exception e) {
                    logger.warn("更新分镜视频任务的 TaskStatus externalTaskId 失败: taskId={}", taskId, e);
                }
            });
        } catch (Exception e) {
            logger.error("保存视频任务ID失败: {}", e.getMessage(), e);
            // 不抛出异常，因为任务已经提交成功
        }
    }
    
    /**
     * 使用只读事务获取分镜视频任务（快速完成，避免连接泄漏）
     * 注意：使用 TransactionTemplate 而非 @Transactional，因为私有方法上的注解不生效
     */
    private StoryboardVideoTask getStoryboardVideoTaskWithTransaction(String taskId) {
        return readOnlyTransactionTemplate.execute(status -> {
            Optional<StoryboardVideoTask> taskOpt = storyboardVideoTaskRepository.findByTaskId(taskId);
            if (!taskOpt.isPresent()) {
                throw new RuntimeException("找不到分镜视频任务: " + taskId);
            }
            return taskOpt.get();
        });
    }

    /**
     * 将图片文件转换为Base64
     */
    private String convertImageFileToBase64(String imageUrl) {
        try {
            // 检查是否是相对路径
            if (!imageUrl.startsWith("http://") && !imageUrl.startsWith("https://")) {
                // 从本地文件系统读取图片
                java.nio.file.Path imagePath = java.nio.file.Paths.get(imageUrl);
                
                // 如果文件不存在，尝试使用绝对路径
                if (!java.nio.file.Files.exists(imagePath)) {
                    // 获取当前工作目录并构建绝对路径
                    String currentDir = System.getProperty("user.dir");
                    java.nio.file.Path absolutePath = java.nio.file.Paths.get(currentDir, imageUrl);
                    logger.info("当前工作目录: {}", currentDir);
                    logger.info("尝试绝对路径: {}", absolutePath);
                    
                    if (java.nio.file.Files.exists(absolutePath)) {
                        imagePath = absolutePath;
                        logger.info("找到图片文件: {}", absolutePath);
                    } else {
                        // 尝试使用uploadPath配置作为基础路径
                        java.nio.file.Path uploadBasePath = java.nio.file.Paths.get(currentDir, uploadPath, imageUrl);
                        logger.info("尝试上传路径: {}", uploadBasePath);
                        
                        if (java.nio.file.Files.exists(uploadBasePath)) {
                            imagePath = uploadBasePath;
                            logger.info("找到图片文件(上传路径): {}", uploadBasePath);
                        } else {
                            throw new RuntimeException("图片文件不存在: " + imageUrl + 
                                ", 当前目录: " + absolutePath + 
                                ", 上传路径: " + uploadBasePath + 
                                ". 请确保文件路径正确或使用完整URL");
                        }
                    }
                }
                
                byte[] imageBytes = java.nio.file.Files.readAllBytes(imagePath);
                return realAIService.convertImageToBase64(imageBytes, "image/jpeg");
            } else {
                // 从URL读取图片内容
                kong.unirest.HttpResponse<byte[]> response = kong.unirest.Unirest.get(imageUrl)
                    .asBytes();
                
                if (response.getStatus() == 200 && response.getBody() != null) {
                    // 使用RealAIService的convertImageToBase64方法
                    return realAIService.convertImageToBase64(response.getBody(), "image/jpeg");
                } else {
                    throw new RuntimeException("无法从URL读取图片: " + imageUrl + ", 状态码: " + response.getStatus());
                }
            }
        } catch (Exception e) {
            logger.error("读取图片文件失败: {}", imageUrl, e);
            throw new RuntimeException("图片文件读取失败: " + e.getMessage());
        }
    }

    /**
     * 从API响应中提取真实任务ID
     */
    private String extractRealTaskId(Map<String, Object> apiResponse) {
        if (apiResponse == null) {
            return null;
        }
        
        // 首先检查顶层是否有task_id（新API格式）
        if (apiResponse.containsKey("task_id")) {
            Object taskIdObj = apiResponse.get("task_id");
            if (taskIdObj != null) {
                return taskIdObj.toString();
            }
        }

        // 然后检查data字段
        if (!apiResponse.containsKey("data")) {
            return null;
        }

        Object data = apiResponse.get("data");
        
        if (data instanceof Map) {
            Map<?, ?> dataMap = (Map<?, ?>) data;
            // 检查新格式的task_id
            Object taskIdObj = dataMap.get("task_id");
            if (taskIdObj != null) {
                return taskIdObj.toString();
            }
            // 检查旧格式的taskNo
            Object taskNoObj = dataMap.get("taskNo");
            if (taskNoObj != null) {
                return taskNoObj.toString();
            }
            // 检查旧格式的taskId
            Object taskIdObj2 = dataMap.get("taskId");
            if (taskIdObj2 != null) {
                return taskIdObj2.toString();
            }
        } else if (data instanceof List) {
            List<?> dataList = (List<?>) data;
            if (!dataList.isEmpty()) {
                Object firstElement = dataList.get(0);
                if (firstElement instanceof Map) {
                    Map<?, ?> firstMap = (Map<?, ?>) firstElement;
                    // 检查新格式的task_id
                    Object taskIdObj = firstMap.get("task_id");
                    if (taskIdObj != null) {
                        return taskIdObj.toString();
                    }
                    // 检查旧格式的taskNo
                    Object taskNoObj = firstMap.get("taskNo");
                    if (taskNoObj != null) {
                        return taskNoObj.toString();
                    }
                    // 检查旧格式的taskId
                    Object taskIdObj2 = firstMap.get("taskId");
                    if (taskIdObj2 != null) {
                        return taskIdObj2.toString();
                    }
                }
            }
        }
        
        logger.warn("无法从API响应中提取任务ID");
        return null;
    }

    /**
     * 检查队列中的任务状态 - 每2分钟轮询查询
     * 查询正在处理的任务，调用外部API获取最新状态
     * 注意：此方法明确禁用事务，因为长时间运行的外部API调用会占用数据库连接
     */
    @Transactional(propagation = Propagation.NOT_SUPPORTED)
    public void checkTaskStatuses() {
        // 快速查询待检查任务（使用只读事务）
        List<TaskQueue> tasksToCheck = getTasksToCheck();
        
        logger.info("轮询查询待检查任务数量: {}", tasksToCheck.size());
        
        if (tasksToCheck.isEmpty()) {
            logger.debug("没有需要检查的任务");
            return;
        }
        
        for (TaskQueue taskQueue : tasksToCheck) {
            try {
                checkTaskStatusInternal(taskQueue);
            } catch (Exception e) {
                logger.error("检查任务状态失败: taskId={}", taskQueue.getTaskId(), e);
                // 继续检查其他任务，不中断整个流程
            }
        }
    }

    /**
     * 获取待检查任务列表（使用 TransactionTemplate 手动管理事务，快速完成）
     */
    public List<TaskQueue> getTasksToCheck() {
        return readOnlyTransactionTemplate.execute(status -> {
            return taskQueueRepository.findTasksToCheck();
        });
    }
    
    /**
     * 检查是否有待处理的任务（快速检查，只统计数量）
     * @return true 如果有待处理任务，false 否则
     */
    public boolean hasTasksToCheck() {
        return readOnlyTransactionTemplate.execute(status -> {
            return taskQueueRepository.countTasksToCheck() > 0;
        });
    }

    /**
     * 检查单个任务状态 - 公共方法
     * 供轮询查询服务调用
     */
    @Transactional(propagation = Propagation.NOT_SUPPORTED)
    public void checkTaskStatus(TaskQueue taskQueue) {
        checkTaskStatusInternal(taskQueue);
    }

    /**
     * 检查单个任务状态 - 轮询查询外部API
     * 每2分钟调用一次，获取任务最新状态
     * 注意：此方法明确禁用事务，因为长时间运行的外部API调用会占用数据库连接
     */
    @Transactional(propagation = Propagation.NOT_SUPPORTED)
    private void checkTaskStatusInternal(TaskQueue taskQueue) {
        String taskId = taskQueue.getTaskId();
        
        logger.info("开始检查任务状态: taskId={}, taskType={}, realTaskId={}", 
            taskId, taskQueue.getTaskType(), taskQueue.getRealTaskId());
        
        // 检查是否正在查询此任务，如果是则跳过（防止重复查询）
        if (!checkingTasks.add(taskId)) {
            logger.debug("任务 {} 正在被其他线程检查，跳过", taskId);
            return;
        }

        try {
            // 特殊处理：分镜视频任务需要检查多个视频任务
            if (taskQueue.getTaskType() == CommonTaskType.STORYBOARD_VIDEO) {
                logger.info("分镜视频任务，调用专门的检查方法: taskId={}", taskId);
                checkStoryboardVideoTasks(taskQueue);
                return;
            }

            if (taskQueue.getRealTaskId() == null || taskQueue.getRealTaskId().isEmpty() 
                || "<no value>".equals(taskQueue.getRealTaskId())) {
                // 增加时间窗口保护：只有任务创建超过5分钟后 realTaskId 仍为空才标记为失败
                // 避免任务刚创建还在等待外部API响应时就被错误地标记为失败
                java.time.LocalDateTime fiveMinutesAgo = java.time.LocalDateTime.now().minusMinutes(5);
                if (taskQueue.getCreatedAt() != null && taskQueue.getCreatedAt().isAfter(fiveMinutesAgo)) {
                    logger.debug("任务 {} 的 realTaskId 为空，但创建时间未超过5分钟，跳过本次检查", taskId);
                    return;
                }
                
                logger.warn("任务 {} 的 realTaskId 为空且已超过5分钟，标记为失败并返还积分", taskId);
                
                // 标记任务为失败
                String errorMessage = "任务提交失败：未能成功提交到外部API，请检查网络或稍后重试";
                taskStatusPollingService.markTaskFailed(taskId, errorMessage);
                
                // 更新业务表状态
                fallbackUpdateBusinessTaskFailed(taskId, errorMessage);
                
                // 返还积分
                try {
                    userService.returnFrozenPoints(taskId);
                    logger.info("已返还任务 {} 的冻结积分", taskId);
                } catch (Exception e) {
                    logger.warn("返还积分失败（可能已处理）: taskId={}", taskId);
                }
                
                // 从队列中移除
                taskQueue.setStatus(TaskQueue.QueueStatus.FAILED);
                taskQueueRepository.save(taskQueue);
                
                return;
            }
            
            // 查询外部API状态
            logger.info("调用外部API查询任务状态: taskId={}, realTaskId={}", taskId, taskQueue.getRealTaskId());
            Map<String, Object> statusResponse = realAIService.getTaskStatus(taskQueue.getRealTaskId());
            
            // API调用成功后增加检查次数（使用独立事务，快速完成）
            incrementCheckCountWithTransaction(taskQueue.getTaskId());
            
            if (statusResponse != null && statusResponse.containsKey("data")) {
                Object data = statusResponse.get("data");
                
                Map<?, ?> taskData = null;
                
                // 处理不同的响应格式
                if (data instanceof Map) {
                    taskData = (Map<?, ?>) data;
                } else if (data instanceof List) {
                    List<?> dataList = (List<?>) data;
                    if (!dataList.isEmpty()) {
                        Object firstElement = dataList.get(0);
                        if (firstElement instanceof Map) {
                            taskData = (Map<?, ?>) firstElement;
                        }
                    }
                }
                
                if (taskData != null) {
                    logger.info("任务状态响应: taskId={}, taskData={}", taskQueue.getTaskId(), taskData);
                    
                    // 处理嵌套格式：{code=success, data={status=processing, url=...}}
                    if (taskData.containsKey("code") && taskData.containsKey("data")) {
                        Object innerData = taskData.get("data");
                        if (innerData instanceof Map) {
                            taskData = (Map<?, ?>) innerData;
                            logger.info("检测到嵌套格式，提取内层data: {}", taskData);
                        }
                    }
                    
                    String status = (String) taskData.get("status");
                    // 支持大小写不敏感的状态检查
                    if (status != null) {
                        status = status.toUpperCase();
                    }
                    logger.info("解析到的状态: taskId={}, status={}", taskQueue.getTaskId(), status);
                    
                    // 提取结果URL - 支持多种格式
                    String resultUrl = null;
                    
                    // 格式1: sora2 格式 data.output
                    Object dataField = taskData.get("data");
                    if (dataField instanceof Map) {
                        Map<?, ?> dataMap = (Map<?, ?>) dataField;
                        Object output = dataMap.get("output");
                        if (output != null) {
                            String outputStr = output.toString();
                            if (!outputStr.trim().isEmpty() && !outputStr.equals("null")) {
                                resultUrl = outputStr;
                            }
                        }
                    }
                    
                    // 格式2: 直接在根级别的 output 字段
                    if (resultUrl == null) {
                        Object output = taskData.get("output");
                        if (output != null) {
                            String outputStr = output.toString();
                            if (!outputStr.trim().isEmpty() && !outputStr.equals("null")) {
                                resultUrl = outputStr;
                            }
                        }
                    }
                    
                    // 格式3: 直接在根级别的 url 字段（新API格式）
                    if (resultUrl == null) {
                        Object urlField = taskData.get("url");
                        if (urlField != null) {
                            String urlStr = urlField.toString();
                            if (!urlStr.trim().isEmpty() && !urlStr.equals("null")) {
                                resultUrl = urlStr;
                            }
                        }
                    }
                    
                    logger.info("解析到的结果URL: taskId={}, resultUrl={}", taskQueue.getTaskId(), 
                        resultUrl != null ? (resultUrl.length() > 100 ? resultUrl.substring(0, 100) + "..." : resultUrl) : "null");
                    
                    // 提取错误消息
                    String errorMessage = (String) taskData.get("errorMessage");
                    if (errorMessage == null) {
                        errorMessage = (String) taskData.get("fail_reason");
                    }
                    if (errorMessage == null) {
                        errorMessage = (String) taskData.get("error");
                    }

                    // 优化后的任务状态判断逻辑
                    // 1. 任务完成：状态为COMPLETED/SUCCESS且有有效的resultUrl
                    if (("COMPLETED".equals(status) || "SUCCESS".equals(status)) && 
                        resultUrl != null && !resultUrl.trim().isEmpty()) {
                        logger.info("任务完成: taskId={}, resultUrl={}", taskQueue.getTaskId(), 
                            resultUrl.length() > 100 ? resultUrl.substring(0, 100) + "..." : resultUrl);
                        updateTaskAsCompleted(taskQueue, resultUrl);
                    } 
                    // 2. 任务失败：状态为FAILED/ERROR
                    else if ("FAILED".equals(status) || "ERROR".equals(status)) {
                        logger.warn("任务失败: taskId={}, error={}", taskQueue.getTaskId(), errorMessage);
                        updateTaskAsFailed(taskQueue, errorMessage);
                    }
                    // 3. 处理中状态：不输出日志，避免重复（静默轮询）
                    else if ("PROCESSING".equals(status) || "IN_PROGRESS".equals(status)) {
                        // 静默处理，等待下次轮询
                    }
                    // 4. 其他未知状态：记录警告
                    else {
                        logger.debug("任务状态未变化: taskId={}, status={}", taskQueue.getTaskId(), status);
                    }
                }
            }

            // 检查是否超时
            if (taskQueue.isTimeout()) {
                logger.warn("任务超时: taskId={}", taskQueue.getTaskId());
                updateTaskAsTimeout(taskQueue);
            }

        } catch (Exception e) {
            logger.warn("查询任务状态异常: taskId={}, 继续轮询", taskQueue.getTaskId(), e);
        } finally {
            // 从正在查询的集合中移除任务ID，允许下次查询
            checkingTasks.remove(taskId);
        }
    }

    /**
     * 检查分镜视频任务状态
     * 优化：现在只生成一个视频任务（将6张分镜图拼接后调用图生视频接口）
     * 不再需要视频拼接逻辑
     */
    @Transactional(propagation = Propagation.NOT_SUPPORTED)
    private void checkStoryboardVideoTasks(TaskQueue taskQueue) {
        try {
            // 获取分镜视频任务
            StoryboardVideoTask storyboardTask = getStoryboardVideoTaskWithTransaction(taskQueue.getTaskId());
            if (storyboardTask == null) {
                logger.warn("分镜视频任务不存在: {}", taskQueue.getTaskId());
                return;
            }

            // 使用realTaskId检查单个视频任务状态（现在只有一个视频任务）
            String realTaskId = storyboardTask.getRealTaskId();
            if (realTaskId == null || realTaskId.isEmpty()) {
                // 向后兼容：检查taskQueue的realTaskId
                if (taskQueue.getRealTaskId() != null && !taskQueue.getRealTaskId().isEmpty()) {
                    realTaskId = taskQueue.getRealTaskId();
            } else {
                    logger.warn("分镜视频任务没有realTaskId: {}", taskQueue.getTaskId());
                    return;
                }
            }

            // 检查单个视频任务状态（使用图生视频任务的检查逻辑）
            checkTaskStatusInternalForSingleTask(taskQueue);

            // 增加检查次数
            incrementCheckCountWithTransaction(taskQueue.getTaskId());
        } catch (Exception e) {
            logger.error("检查分镜视频任务状态失败: {}", taskQueue.getTaskId(), e);
        }
    }

    /**
     * 从任务数据中提取视频URL（支持多种API响应格式）
     */
    private String extractVideoUrl(Map<?, ?> taskData) {
        String resultUrl = null;
        
        // 0. 优先检查 sora2 格式：data.output
        Object dataField = taskData.get("data");
        if (dataField instanceof Map) {
            Map<?, ?> dataMap = (Map<?, ?>) dataField;
            Object output = dataMap.get("output");
            if (output != null) {
                String outputStr = output.toString();
                // 检查是否为空字符串或"null"字符串
                if (!outputStr.trim().isEmpty() && !outputStr.equals("null")) {
                    resultUrl = outputStr;
                }
            }
        }
        
        // 1. 检查直接的task_result.videos[0].url（Kling API格式）
        if (resultUrl == null) {
            Object directTaskResult = taskData.get("task_result");
            if (directTaskResult instanceof Map) {
                Map<?, ?> directTaskResultMap = (Map<?, ?>) directTaskResult;
                Object directVideos = directTaskResultMap.get("videos");
                if (directVideos instanceof java.util.List && !((java.util.List<?>) directVideos).isEmpty()) {
                    Object firstVideo = ((java.util.List<?>) directVideos).get(0);
                    if (firstVideo instanceof Map) {
                        Map<?, ?> videoMap = (Map<?, ?>) firstVideo;
                        Object url = videoMap.get("url");
                        if (url != null) {
                            resultUrl = url.toString();
                        }
                    }
                }
            }
        }
        
        // 2. 检查嵌套的data.data.task_result.videos[0].url（Kling API嵌套格式）
        if (resultUrl == null && dataField instanceof Map) {
            Map<?, ?> nestedDataMap = (Map<?, ?>) dataField;
            Object innerData = nestedDataMap.get("data");
            if (innerData instanceof Map) {
                Map<?, ?> innerDataMap = (Map<?, ?>) innerData;
                
                // 检查 task_result.videos[0].url
                Object taskResult = innerDataMap.get("task_result");
                if (taskResult instanceof Map) {
                    Map<?, ?> taskResultMap = (Map<?, ?>) taskResult;
                    Object videos = taskResultMap.get("videos");
                    if (videos instanceof java.util.List && !((java.util.List<?>) videos).isEmpty()) {
                        Object firstVideo = ((java.util.List<?>) videos).get(0);
                        if (firstVideo instanceof Map) {
                            Map<?, ?> videoMap = (Map<?, ?>) firstVideo;
                            Object url = videoMap.get("url");
                            if (url != null) {
                                resultUrl = url.toString();
                            }
                        }
                    }
                }
                
                // 如果上面没找到，检查output字段
                if (resultUrl == null) {
                    Object output = innerDataMap.get("output");
                    if (output != null && !output.toString().equals("null")) {
                        resultUrl = output.toString();
                    }
                }
            }
        }
        
        // 3. 检查直接的output字段（顶层output）
        if (resultUrl == null) {
            Object output = taskData.get("output");
            if (output != null && !output.toString().equals("null")) {
                resultUrl = output.toString();
            }
        }
        
        // 4. 检查其他可能的字段名（兼容旧格式）
        if (resultUrl == null) {
            Object resultUrlObj = taskData.get("resultUrl");
            if (resultUrlObj != null) {
                resultUrl = resultUrlObj.toString();
            }
        }
        if (resultUrl == null) {
            Object videoUrl = taskData.get("video_url");
            if (videoUrl != null) {
                resultUrl = videoUrl.toString();
            }
        }
        if (resultUrl == null) {
            Object url = taskData.get("url");
            if (url != null) {
                resultUrl = url.toString();
            }
        }
        if (resultUrl == null) {
            Object result = taskData.get("result");
            if (result != null) {
                resultUrl = result.toString();
            }
        }
        
        return resultUrl;
    }

    /**
     * 检查单个任务状态（用于向后兼容）
     */
    @Transactional(propagation = Propagation.NOT_SUPPORTED)
    private void checkTaskStatusInternalForSingleTask(TaskQueue taskQueue) {
        if (taskQueue.getRealTaskId() == null) {
            return;
        }

        try {
            // 查询外部API状态
            Map<String, Object> statusResponse = realAIService.getTaskStatus(taskQueue.getRealTaskId());
            
            // ⭐ 添加详细日志：输出完整的API响应
            logger.info("🔍 [API响应] taskId={}, realTaskId={}, response={}", 
                taskQueue.getTaskId(), taskQueue.getRealTaskId(), statusResponse);
            
            // API调用成功后增加检查次数（使用独立事务，快速完成）
            incrementCheckCountWithTransaction(taskQueue.getTaskId());
            
            if (statusResponse != null && statusResponse.containsKey("data")) {
                Object data = statusResponse.get("data");
                
                logger.info("🔍 [data字段] taskId={}, data={}", taskQueue.getTaskId(), data);
                
                Map<?, ?> taskData = null;
                
                // 处理不同的响应格式
                if (data instanceof Map) {
                    taskData = (Map<?, ?>) data;
                } else if (data instanceof List) {
                    List<?> dataList = (List<?>) data;
                    if (!dataList.isEmpty()) {
                        Object firstElement = dataList.get(0);
                        if (firstElement instanceof Map) {
                            taskData = (Map<?, ?>) firstElement;
                        }
                    }
                }
                
                if (taskData != null) {
                    logger.info("🔍 [taskData内容] taskId={}, taskData={}", taskQueue.getTaskId(), taskData);
                    
                    String status = (String) taskData.get("status");
                    // 支持大小写不敏感的状态检查
                    if (status != null) {
                        status = status.toUpperCase();
                    }
                    
                    // 提取结果URL（只支持sora2格式）
                    String resultUrl = extractVideoUrl(taskData);
                    
                    // 提取错误消息
                    String errorMessage = (String) taskData.get("errorMessage");
                    if (errorMessage == null) {
                        errorMessage = (String) taskData.get("fail_reason");
                    }
                    if (errorMessage == null) {
                        errorMessage = (String) taskData.get("error");
                    }

                    // 优化后的任务状态判断逻辑
                    // 1. 任务完成：状态为COMPLETED/SUCCESS且有有效的resultUrl
                    if (("COMPLETED".equals(status) || "SUCCESS".equals(status)) && 
                        resultUrl != null && !resultUrl.trim().isEmpty()) {
                        logger.info("✅ 任务完成: taskId={}, resultUrl={}", taskQueue.getTaskId(), 
                            resultUrl.length() > 100 ? resultUrl.substring(0, 100) + "..." : resultUrl);
                        updateTaskAsCompleted(taskQueue, resultUrl);
                    } 
                    // 1b. 状态为完成但没有resultUrl - 记录警告并输出完整响应以便调试
                    else if ("COMPLETED".equals(status) || "SUCCESS".equals(status)) {
                        logger.warn("⚠️ 任务状态为COMPLETED但未找到有效的resultUrl! taskId={}, API响应: {}", 
                            taskQueue.getTaskId(), statusResponse);
                        logger.warn("⚠️ taskData详细信息: {}", taskData);
                        // 暂不标记为失败，继续轮询，可能是API延迟更新
                    }
                    // 2. 任务失败：状态为FAILED/ERROR
                    else if ("FAILED".equals(status) || "ERROR".equals(status)) {
                        logger.warn("❌ 任务失败: taskId={}, error={}", taskQueue.getTaskId(), errorMessage);
                        updateTaskAsFailed(taskQueue, errorMessage);
                    }
                }
            }
        } catch (Exception e) {
            logger.warn("查询任务状态异常: taskId={}, 继续轮询", taskQueue.getTaskId(), e);
        }
    }

    /**
     * 更新任务为完成状态（使用独立事务，快速完成）
     * COS上传在事务外执行，避免事务超时
     */
    private void updateTaskAsCompleted(TaskQueue taskQueue, String resultUrl) {
        String taskId = taskQueue.getTaskId();
        
        // 第一步：在事务中完成核心数据库更新
        boolean transactionSuccess = false;
        try {
            transactionTemplate.executeWithoutResult(status -> {
                try {
                    // 使用悲观锁查询，防止多线程并发重复处理
                    TaskQueue freshTaskQueue = entityManager.find(
                        TaskQueue.class, 
                        taskQueue.getId(), 
                        LockModeType.PESSIMISTIC_WRITE
                    );
            
                    if (freshTaskQueue == null) {
                        logger.error("任务不存在: {}", taskQueue.getId());
                        return;
                    }
            
                    if (freshTaskQueue.getStatus() == TaskQueue.QueueStatus.COMPLETED) {
                        logger.info("任务已完成，跳过重复处理: {}", taskId);
                        return;
                    }
            
                    freshTaskQueue.updateStatus(TaskQueue.QueueStatus.COMPLETED);
                    taskQueueRepository.save(freshTaskQueue);

                    // 扣除冻结的积分（内部已处理重复扣除的情况）
                    try {
                        // 分镜视频使用 taskId + "_vid" 作为积分冻结ID
                        if (freshTaskQueue.getTaskType() == CommonTaskType.STORYBOARD_VIDEO) {
                            userService.deductFrozenPoints(taskId + "_vid");
                        } else {
                            userService.deductFrozenPoints(taskId);
                        }
                    } catch (Exception e) {
                        // 积分扣除失败不影响任务完成状态
                        logger.warn("积分扣除失败（不影响任务完成）: taskId={}, error={}", taskId, e.getMessage());
                    }

                    // 任务完成后从 task_queue 中删除记录
                    try {
                        taskQueueRepository.delete(freshTaskQueue);
                        logger.info("任务完成，已从 task_queue 中删除: {}", taskId);
                    } catch (Exception deleteException) {
                        logger.warn("删除 task_queue 记录失败: {}", taskId, deleteException);
                    }
                } catch (Exception e) {
                    logger.error("更新任务完成状态失败: {}", taskId, e);
                    status.setRollbackOnly();
                    throw e;
                }
            });
            transactionSuccess = true;
        } catch (Exception e) {
            logger.error("执行更新任务完成状态事务失败: {}", taskId, e);
        }

        // 第二步：在事务外执行COS上传（避免事务超时）
        String finalResultUrl = resultUrl;
        if (resultUrl != null && !resultUrl.isEmpty() && cosService.isEnabled()) {
            try {
                logger.info("开始上传视频到COS（事务外）: taskId={}", taskId);
                String cosUrl = cosService.uploadVideoFromUrl(resultUrl, null);
                if (cosUrl != null && !cosUrl.isEmpty()) {
                    logger.info("COS上传成功: taskId={}, cosUrl={}", taskId, cosUrl);
                    finalResultUrl = cosUrl;
                } else {
                    logger.warn("COS上传返回空URL，使用原始URL: taskId={}", taskId);
                }
            } catch (Exception cosException) {
                logger.error("上传视频到COS失败，使用原始URL: taskId={}", taskId, cosException);
            }
        }

        // 第三步：更新业务表和UserWork（使用级联更新，避免锁竞争）
        if (finalResultUrl != null && !finalResultUrl.isEmpty()) {
            try {
                // 通过 TaskStatusPollingService 的级联更新来更新所有相关表
                // 这样可以避免多个事务同时更新同一条记录
                boolean updated = taskStatusPollingService.updateTaskStatusWithCascade(
                    taskId, 
                    TaskStatus.Status.COMPLETED, 
                    finalResultUrl, 
                    null
                );
                if (updated) {
                    logger.info("任务完成状态已通过级联更新: taskId={}", taskId);
                } else {
                    // 级联更新失败，回退到直接更新
                    logger.warn("级联更新失败，回退到直接更新: taskId={}", taskId);
                    updateOriginalTaskStatus(taskQueue, "COMPLETED", finalResultUrl, null);
                    try {
                        userWorkService.createWorkFromTask(taskId, finalResultUrl);
                    } catch (Exception e) {
                        logger.warn("创建/更新用户作品失败: {}", taskId, e);
                    }
                }
            } catch (Exception e) {
                logger.error("更新任务完成状态失败: {}", taskId, e);
            }
        }
    }
    
    /**
     * 更新结果URL为COS URL（独立事务）
     * 只更新URL字段，不重复更新状态
     */
    private void updateResultUrlToCos(TaskQueue taskQueue, String cosUrl) {
        String taskId = taskQueue.getTaskId();
        try {
            transactionTemplate.executeWithoutResult(status -> {
                try {
                    // 直接更新业务表的resultUrl（不通过updateOriginalTaskStatus，避免重复更新）
                    if (taskQueue.getTaskType() == CommonTaskType.STORYBOARD_VIDEO) {
                        storyboardVideoTaskRepository.findByTaskId(taskId).ifPresent(task -> {
                            task.setResultUrl(cosUrl);
                            // 同时更新videoUrls
                            if (!cosUrl.startsWith("data:image")) {
                                task.setVideoUrls("[\"" + cosUrl + "\"]");
                            }
                            storyboardVideoTaskRepository.save(task);
                            logger.info("已更新StoryboardVideoTask的COS URL: taskId={}", taskId);
                        });
                    } else if (taskQueue.getTaskType() == CommonTaskType.IMAGE_TO_VIDEO) {
                        imageToVideoTaskRepository.findByTaskId(taskId).ifPresent(task -> {
                            task.setResultUrl(cosUrl);
                            imageToVideoTaskRepository.save(task);
                            logger.info("已更新ImageToVideoTask的COS URL: taskId={}", taskId);
                        });
                    } else if (taskQueue.getTaskType() == CommonTaskType.TEXT_TO_VIDEO) {
                        textToVideoTaskRepository.findByTaskId(taskId).ifPresent(task -> {
                            task.setResultUrl(cosUrl);
                            textToVideoTaskRepository.save(task);
                            logger.info("已更新TextToVideoTask的COS URL: taskId={}", taskId);
                        });
                    }
                    
                    // 更新用户作品的resultUrl（使用简单更新，不创建新记录）
                    try {
                        userWorkService.updateWorkResultUrl(taskId, cosUrl);
                    } catch (Exception e) {
                        logger.warn("更新UserWork COS URL失败: {}", taskId, e);
                    }
                    
                    // 更新task_status的resultUrl
                    try {
                        TaskStatus taskStatus = taskStatusPollingService.getTaskStatus(taskId);
                        if (taskStatus != null) {
                            taskStatus.setResultUrl(cosUrl);
                            taskStatusPollingService.saveOrUpdateTaskStatus(taskStatus);
                        }
                    } catch (Exception e) {
                        logger.warn("更新task_status COS URL失败: {}", taskId, e);
                    }
                    
                    logger.info("已更新resultUrl为COS URL: taskId={}", taskId);
                } catch (Exception e) {
                    logger.error("更新COS URL失败: {}", taskId, e);
                }
            });
        } catch (Exception e) {
            logger.error("执行更新COS URL事务失败: {}", taskId, e);
        }
    }

    /**
     * 根据任务ID处理任务完成（供 TaskStatusPollingService 调用）
     * @param taskId 任务ID
     * @param resultUrl 结果URL
     */
    public void handleTaskCompletionByTaskId(String taskId, String resultUrl) {
        logger.info("处理任务完成: taskId={}, resultUrl={}", taskId, 
            resultUrl != null && resultUrl.length() > 100 ? resultUrl.substring(0, 100) + "..." : resultUrl);
        
        Optional<TaskQueue> taskQueueOpt = taskQueueRepository.findByTaskId(taskId);
        if (taskQueueOpt.isPresent()) {
            updateTaskAsCompleted(taskQueueOpt.get(), resultUrl);
        } else {
            // TaskQueue 可能已经被删除，直接更新业务表和 UserWork
            logger.info("TaskQueue 中未找到任务 {}，尝试直接更新业务表", taskId);
            updateBusinessTaskAndUserWork(taskId, resultUrl);
        }
    }
    
    /**
     * 根据任务ID处理任务失败（供 TaskStatusPollingService 调用）
     * @param taskId 任务ID
     * @param errorMessage 错误信息
     */
    public void handleTaskFailureByTaskId(String taskId, String errorMessage) {
        logger.info("处理任务失败: taskId={}, error={}", taskId, errorMessage);
        
        Optional<TaskQueue> taskQueueOpt = taskQueueRepository.findByTaskId(taskId);
        if (taskQueueOpt.isPresent()) {
            updateTaskAsFailed(taskQueueOpt.get(), errorMessage);
        } else {
            // TaskQueue 可能已经被删除，直接更新业务表和 UserWork
            logger.info("TaskQueue 中未找到任务 {}，尝试直接更新业务表为失败", taskId);
            updateBusinessTaskAndUserWorkAsFailed(taskId, errorMessage);
        }
    }
    
    /**
     * 通过 task_status 触发级联更新任务完成状态
     * 如果 task_status 记录不存在，回退到直接更新业务表
     */
    private void updateBusinessTaskAndUserWork(String taskId, String resultUrl) {
        try {
            // 尝试通过 task_status 触发级联更新
            boolean updated = taskStatusPollingService.markTaskCompleted(taskId, resultUrl);
            if (updated) {
                logger.info("任务完成状态已更新（级联更新已触发）: {}", taskId);
            } else {
                // 回退：直接更新业务表和 UserWork
                logger.warn("task_status 记录不存在，回退到直接更新业务表: {}", taskId);
                fallbackUpdateBusinessTaskCompleted(taskId, resultUrl);
            }
        } catch (Exception e) {
            logger.error("更新任务完成状态失败: {}", taskId, e);
        }
    }
    
    /**
     * 回退方法：直接更新业务表为完成状态
     */
    private void fallbackUpdateBusinessTaskCompleted(String taskId, String resultUrl) {
        transactionTemplate.executeWithoutResult(status -> {
            if (taskId.startsWith("img2vid_")) {
                imageToVideoTaskRepository.findByTaskId(taskId).ifPresent(task -> {
                    task.setStatus(ImageToVideoTask.TaskStatus.COMPLETED);
                    task.setResultUrl(resultUrl);
                    task.setProgress(100);
                    task.setCompletedAt(java.time.LocalDateTime.now());
                    imageToVideoTaskRepository.save(task);
                    logger.info("回退更新图生视频任务为完成: {}", taskId);
                });
            } else if (taskId.startsWith("storyboard_") || taskId.startsWith("sb_")) {
                storyboardVideoTaskRepository.findByTaskId(taskId).ifPresent(task -> {
                    task.setStatus(StoryboardVideoTask.TaskStatus.COMPLETED);
                    task.setResultUrl(resultUrl);
                    // 如果是视频URL（不是Base64图片），同时设置videoUrls
                    if (resultUrl != null && !resultUrl.startsWith("data:image")) {
                        task.setVideoUrls("[\"" + resultUrl + "\"]");
                        logger.info("回退更新时设置videoUrls: taskId={}", taskId);
                    }
                    task.setProgress(100);
                    task.setCompletedAt(java.time.LocalDateTime.now());
                    storyboardVideoTaskRepository.save(task);
                    logger.info("回退更新分镜视频任务为完成: {}", taskId);
                });
            } else if (taskId.startsWith("txt2vid_")) {
                textToVideoTaskRepository.findByTaskId(taskId).ifPresent(task -> {
                    task.setStatus(TextToVideoTask.TaskStatus.COMPLETED);
                    task.setResultUrl(resultUrl);
                    task.setProgress(100);
                    task.setCompletedAt(java.time.LocalDateTime.now());
                    textToVideoTaskRepository.save(task);
                    logger.info("回退更新文生视频任务为完成: {}", taskId);
                });
            }
            
            // 更新 UserWork
            if (resultUrl != null && !resultUrl.isEmpty()) {
                try {
                    userWorkService.createWorkFromTask(taskId, resultUrl);
                } catch (Exception e) {
                    logger.warn("回退更新 UserWork 失败: {}", taskId, e);
                }
            }
        });
    }
    
    /**
     * 通过 task_status 触发级联更新任务失败状态
     * 如果 task_status 记录不存在，回退到直接更新业务表
     */
    private void updateBusinessTaskAndUserWorkAsFailed(String taskId, String errorMessage) {
        try {
            // 尝试通过 task_status 触发级联更新
            boolean updated = taskStatusPollingService.markTaskFailed(taskId, errorMessage);
            if (updated) {
                logger.info("任务失败状态已更新（级联更新已触发）: {}", taskId);
            } else {
                // 回退：直接更新业务表和 UserWork
                logger.warn("task_status 记录不存在，回退到直接更新业务表: {}", taskId);
                fallbackUpdateBusinessTaskFailed(taskId, errorMessage);
            }
        } catch (Exception e) {
            logger.error("更新任务失败状态失败: {}", taskId, e);
        }
    }
    
    /**
     * 回退方法：直接更新业务表为失败状态
     */
    private void fallbackUpdateBusinessTaskFailed(String taskId, String errorMessage) {
        transactionTemplate.executeWithoutResult(status -> {
            if (taskId.startsWith("img2vid_")) {
                imageToVideoTaskRepository.findByTaskId(taskId).ifPresent(task -> {
                    task.setStatus(ImageToVideoTask.TaskStatus.FAILED);
                    task.setErrorMessage(errorMessage);
                    task.setCompletedAt(java.time.LocalDateTime.now());
                    imageToVideoTaskRepository.save(task);
                    logger.info("回退更新图生视频任务为失败: {}", taskId);
                });
                // 返还冻结的积分
                try {
                    userService.returnFrozenPoints(taskId);
                    logger.info("回退更新时已返还积分: taskId={}", taskId);
                } catch (Exception e) {
                    logger.debug("回退更新时积分返还失败（可能未冻结）: taskId={}", taskId);
                }
            } else if (taskId.startsWith("storyboard_") || taskId.startsWith("sb_")) {
                storyboardVideoTaskRepository.findByTaskId(taskId).ifPresent(task -> {
                    task.setStatus(StoryboardVideoTask.TaskStatus.FAILED);
                    task.setErrorMessage(errorMessage);
                    task.setCompletedAt(java.time.LocalDateTime.now());
                    storyboardVideoTaskRepository.save(task);
                    logger.info("回退更新分镜视频任务为失败: {}", taskId);
                });
                // 分镜任务需要同时返还 _vid 和 _img 积分
                try {
                    userService.returnFrozenPoints(taskId + "_vid");
                    logger.info("回退更新时已返还分镜视频积分: taskId={}_vid", taskId);
                } catch (Exception e) {
                    logger.debug("回退更新时分镜视频积分返还失败（可能未冻结）: taskId={}_vid", taskId);
                }
                try {
                    userService.returnFrozenPoints(taskId + "_img");
                    logger.info("回退更新时已返还分镜图积分: taskId={}_img", taskId);
                } catch (Exception e) {
                    logger.debug("回退更新时分镜图积分返还失败（可能未冻结）: taskId={}_img", taskId);
                }
            } else if (taskId.startsWith("txt2vid_")) {
                textToVideoTaskRepository.findByTaskId(taskId).ifPresent(task -> {
                    task.setStatus(TextToVideoTask.TaskStatus.FAILED);
                    task.setErrorMessage(errorMessage);
                    task.setCompletedAt(java.time.LocalDateTime.now());
                    textToVideoTaskRepository.save(task);
                    logger.info("回退更新文生视频任务为失败: {}", taskId);
                });
                // 返还冻结的积分
                try {
                    userService.returnFrozenPoints(taskId);
                    logger.info("回退更新时已返还积分: taskId={}", taskId);
                } catch (Exception e) {
                    logger.debug("回退更新时积分返还失败（可能未冻结）: taskId={}", taskId);
                }
            }
            
            // 更新 UserWork
            try {
                userWorkService.updateWorkStatus(taskId, UserWork.WorkStatus.FAILED);
            } catch (Exception e) {
                logger.warn("回退更新 UserWork 状态失败: {}", taskId, e);
            }
            
            // 记录错误日志（从业务表获取用户名）
            try {
                String username = null;
                String taskType = "UNKNOWN";
                
                if (taskId.startsWith("img2vid_")) {
                    taskType = "IMAGE_TO_VIDEO";
                    username = imageToVideoTaskRepository.findByTaskId(taskId)
                        .map(t -> t.getUsername()).orElse(null);
                } else if (taskId.startsWith("storyboard_") || taskId.startsWith("sb_")) {
                    taskType = "STORYBOARD_VIDEO";
                    username = storyboardVideoTaskRepository.findByTaskId(taskId)
                        .map(t -> t.getUsername()).orElse(null);
                } else if (taskId.startsWith("txt2vid_")) {
                    taskType = "TEXT_TO_VIDEO";
                    username = textToVideoTaskRepository.findByTaskId(taskId)
                        .map(t -> t.getUsername()).orElse(null);
                }
                
                if (username != null) {
                    userErrorLogService.logErrorAsync(
                        username,
                        com.example.demo.model.UserErrorLog.ErrorType.TASK_PROCESSING_ERROR,
                        errorMessage,
                        "TaskQueueService.fallbackUpdateBusinessTaskFailed",
                        taskId,
                        taskType
                    );
                    logger.info("已记录错误日志: taskId={}, username={}", taskId, username);
                }
            } catch (Exception logException) {
                logger.warn("记录错误日志失败: {}", taskId, logException);
            }
        });
    }

    /**
     * 更新任务为失败状态（使用独立事务，快速完成）
     * 使用 TransactionTemplate 确保事务正确执行（因为私有方法无法使用 @Transactional）
     */
    private void updateTaskAsFailed(TaskQueue taskQueue, String errorMessage) {
        try {
            // 使用 TransactionTemplate 确保在事务中执行
            transactionTemplate.executeWithoutResult(status -> {
		try {
            taskQueue.updateStatus(TaskQueue.QueueStatus.FAILED);
            taskQueue.setErrorMessage(errorMessage);
            taskQueueRepository.save(taskQueue);

            // 返还冻结的积分
            try {
                // 分镜视频使用 taskId + "_vid" 作为积分冻结ID
                // 分镜图使用 taskId + "_img" 作为积分冻结ID
                if (taskQueue.getTaskType() == CommonTaskType.STORYBOARD_VIDEO) {
                    // 尝试返还视频积分
                    try {
                        userService.returnFrozenPoints(taskQueue.getTaskId() + "_vid");
                        logger.info("已返还分镜视频积分: taskId={}_vid", taskQueue.getTaskId());
                    } catch (Exception e) {
                        logger.debug("分镜视频积分返还失败（可能未冻结）: taskId={}_vid", taskQueue.getTaskId());
                    }
                    // 同时尝试返还分镜图积分（如果分镜图阶段失败）
                    try {
                        userService.returnFrozenPoints(taskQueue.getTaskId() + "_img");
                        logger.info("已返还分镜图积分: taskId={}_img", taskQueue.getTaskId());
                    } catch (Exception e) {
                        logger.debug("分镜图积分返还失败（可能未冻结或已返还）: taskId={}_img", taskQueue.getTaskId());
                    }
                } else {
                    userService.returnFrozenPoints(taskQueue.getTaskId());
                }
            } catch (Exception e) {
                logger.warn("积分返还失败（可能积分记录不存在）: taskId={}, error={}", taskQueue.getTaskId(), e.getMessage());
            }

            // 更新原始任务状态
            updateOriginalTaskStatus(taskQueue, "FAILED", null, errorMessage);

            // 记录错误日志
            try {
                userErrorLogService.logErrorAsync(
                    taskQueue.getUsername(),
                    com.example.demo.model.UserErrorLog.ErrorType.TASK_PROCESSING_ERROR,
                    errorMessage,
                    "TaskQueueService",
                    taskQueue.getTaskId(),
                    taskQueue.getTaskType() != null ? taskQueue.getTaskType().name() : "UNKNOWN"
                );
            } catch (Exception logException) {
                logger.warn("记录错误日志失败: {}", taskQueue.getTaskId(), logException);
            }

			// 同步更新用户作品状态为 FAILED，避免前端将失败任务当作进行中任务恢复
			try {
				userWorkService.updateWorkStatus(taskQueue.getTaskId(), UserWork.WorkStatus.FAILED);
			} catch (Exception workException) {
				logger.warn("更新作品状态为FAILED失败: {}", taskQueue.getTaskId(), workException);
			}

            // 更新 task_status 表中的状态为 FAILED（保留记录）
            try {
                TaskStatus taskStatus = taskStatusPollingService.getTaskStatus(taskQueue.getTaskId());
                if (taskStatus != null) {
                    taskStatus.setStatus(TaskStatus.Status.FAILED);
                    taskStatus.setErrorMessage(errorMessage);
                    taskStatus.setUpdatedAt(java.time.LocalDateTime.now());
                    taskStatusPollingService.saveOrUpdateTaskStatus(taskStatus);
                    logger.info("task_status 表状态已更新为 FAILED: {}", taskQueue.getTaskId());
                }
            } catch (Exception statusException) {
                logger.warn("更新 task_status 状态失败: {}", taskQueue.getTaskId(), statusException);
            }
            
            // 任务失败后从 task_queue 中删除记录（task_status 保留）
            try {
                taskQueueRepository.delete(taskQueue);
                logger.info("任务失败，已从 task_queue 中删除: {}", taskQueue.getTaskId());
            } catch (Exception deleteException) {
                logger.warn("删除 task_queue 记录失败: {}", taskQueue.getTaskId(), deleteException);
            }
        } catch (Exception e) {
            logger.error("更新任务失败状态失败: {}", taskQueue.getTaskId(), e);
                    status.setRollbackOnly();
                    throw e;
                }
            });
        } catch (Exception e) {
            logger.error("执行更新任务失败状态事务失败: {}", taskQueue.getTaskId(), e);
            // 如果原始任务状态更新失败，至少保证队列状态正确
        }
    }

    /**
     * 更新任务为超时状态（使用独立事务，快速完成）
     * 使用 TransactionTemplate 确保事务正确执行（因为私有方法无法使用 @Transactional）
     */
    private void updateTaskAsTimeout(TaskQueue taskQueue) {
        try {
            // 使用 TransactionTemplate 确保在事务中执行
            transactionTemplate.executeWithoutResult(status -> {
		try {
            taskQueue.updateStatus(TaskQueue.QueueStatus.FAILED);
            taskQueue.setErrorMessage("任务处理超时");
            taskQueueRepository.save(taskQueue);

            // 返还冻结的积分
            // 分镜视频任务需要同时返还 _vid 和 _img 积分
            if (taskQueue.getTaskType() == CommonTaskType.STORYBOARD_VIDEO) {
                try {
                    userService.returnFrozenPoints(taskQueue.getTaskId() + "_vid");
                    logger.info("已返还分镜视频积分: taskId={}_vid", taskQueue.getTaskId());
                } catch (Exception e) {
                    logger.debug("分镜视频积分返还失败（可能未冻结）: taskId={}_vid", taskQueue.getTaskId());
                }
                try {
                    userService.returnFrozenPoints(taskQueue.getTaskId() + "_img");
                    logger.info("已返还分镜图积分: taskId={}_img", taskQueue.getTaskId());
                } catch (Exception e) {
                    logger.debug("分镜图积分返还失败（可能未冻结）: taskId={}_img", taskQueue.getTaskId());
                }
            } else {
                userService.returnFrozenPoints(taskQueue.getTaskId());
            }

            // 更新原始任务状态
            updateOriginalTaskStatus(taskQueue, "FAILED", null, "任务处理超时");

            // 记录超时错误日志
            try {
                userErrorLogService.logErrorAsync(
                    taskQueue.getUsername(),
                    com.example.demo.model.UserErrorLog.ErrorType.TASK_TIMEOUT,
                    "任务处理超时",
                    "TaskQueueService",
                    taskQueue.getTaskId(),
                    taskQueue.getTaskType() != null ? taskQueue.getTaskType().name() : "UNKNOWN"
                );
            } catch (Exception logException) {
                logger.warn("记录超时错误日志失败: {}", taskQueue.getTaskId(), logException);
            }

			// 超时任务同样标记为 FAILED，防止前端一直恢复到创作页面
			try {
				userWorkService.updateWorkStatus(taskQueue.getTaskId(), UserWork.WorkStatus.FAILED);
			} catch (Exception workException) {
				logger.warn("更新超时任务的作品状态为FAILED失败: {}", taskQueue.getTaskId(), workException);
			}

            // 更新 task_status 表中的状态为 FAILED（超时视为失败）
            try {
                TaskStatus taskStatus = taskStatusPollingService.getTaskStatus(taskQueue.getTaskId());
                if (taskStatus != null) {
                    taskStatus.setStatus(TaskStatus.Status.FAILED);
                    taskStatus.setErrorMessage("任务处理超时");
                    taskStatus.setUpdatedAt(java.time.LocalDateTime.now());
                    taskStatusPollingService.saveOrUpdateTaskStatus(taskStatus);
                    logger.info("task_status 表状态已更新为 FAILED (超时): {}", taskQueue.getTaskId());
                }
            } catch (Exception statusException) {
                logger.warn("更新 task_status 状态失败: {}", taskQueue.getTaskId(), statusException);
            }
            
            // 任务超时后从 task_queue 中删除记录（task_status 保留）
            try {
                taskQueueRepository.delete(taskQueue);
                logger.info("任务超时，已从 task_queue 中删除: {}", taskQueue.getTaskId());
            } catch (Exception deleteException) {
                logger.warn("删除 task_queue 记录失败: {}", taskQueue.getTaskId(), deleteException);
            }
        } catch (Exception e) {
            logger.error("更新任务超时状态失败: {}", taskQueue.getTaskId(), e);
                    status.setRollbackOnly();
                    throw e;
                }
            });
        } catch (Exception e) {
            logger.error("执行更新任务超时状态事务失败: {}", taskQueue.getTaskId(), e);
            // 如果原始任务状态更新失败，至少保证队列状态正确
        }
    }

    /**
     * 更新原始任务状态（使用独立事务，快速完成）
     * 注意：COMPLETED 和 FAILED 状态会尝试通过 task_status 触发级联更新
     */
    private void updateOriginalTaskStatus(TaskQueue taskQueue, String status, String resultUrl, String errorMessage) {
        logger.info("更新原始任务状态: taskId={}, taskType={}, status={}, resultUrl={}", 
            taskQueue.getTaskId(), taskQueue.getTaskType(), status, 
            resultUrl != null ? (resultUrl.length() > 50 ? resultUrl.substring(0, 50) + "..." : resultUrl) : "null");
        try {
            String taskId = taskQueue.getTaskId();
            
            // COMPLETED 和 FAILED 状态尝试通过 task_status 触发级联更新
            if ("COMPLETED".equals(status)) {
                boolean updated = taskStatusPollingService.markTaskCompleted(taskId, resultUrl);
                if (updated) {
                    logger.info("任务完成状态已通过级联更新: taskId={}", taskId);
                    return; // 级联成功，不需要回退
                }
                // 级联失败，继续回退到直接更新
                logger.warn("task_status 记录不存在，回退到直接更新业务表: taskId={}", taskId);
            } else if ("FAILED".equals(status) || "CANCELLED".equals(status)) {
                boolean updated = taskStatusPollingService.markTaskFailed(taskId, errorMessage);
                if (updated) {
                    logger.info("任务失败状态已通过级联更新: taskId={}", taskId);
                    return; // 级联成功，不需要回退
                }
                // 级联失败，继续回退到直接更新
                logger.warn("task_status 记录不存在，回退到直接更新业务表: taskId={}", taskId);
            }
            
            // 回退逻辑：直接更新业务表
            if (taskQueue.getTaskType() == CommonTaskType.TEXT_TO_VIDEO) {
                Optional<TextToVideoTask> taskOpt = textToVideoTaskRepository.findByTaskId(taskId);
                if (taskOpt.isPresent()) {
                    TextToVideoTask task = taskOpt.get();
                    if ("COMPLETED".equals(status)) {
                        task.setResultUrl(resultUrl);
                        task.updateStatus(TextToVideoTask.TaskStatus.COMPLETED);
                        task.updateProgress(100);
                        task.setCompletedAt(java.time.LocalDateTime.now());
                        textToVideoTaskRepository.save(task);
                    } else if ("FAILED".equals(status) || "CANCELLED".equals(status)) {
                        task.updateStatus(TextToVideoTask.TaskStatus.FAILED);
                        task.setErrorMessage(errorMessage);
                        task.setCompletedAt(java.time.LocalDateTime.now());
                        textToVideoTaskRepository.save(task);
                    } else if ("PROCESSING".equals(status)) {
                        // 处理中状态，更新resultUrl以显示进度
                        if (resultUrl != null && !resultUrl.isEmpty()) {
                            task.setResultUrl(resultUrl);
                            textToVideoTaskRepository.save(task);
                        }
                    }
                }
            } else if (taskQueue.getTaskType() == CommonTaskType.IMAGE_TO_VIDEO) {
                Optional<ImageToVideoTask> taskOpt = imageToVideoTaskRepository.findByTaskId(taskId);
                if (taskOpt.isPresent()) {
                    ImageToVideoTask task = taskOpt.get();
                    logger.info("找到ImageToVideoTask: taskId={}, 当前状态={}", taskId, task.getStatus());
                    if ("COMPLETED".equals(status)) {
                        task.setResultUrl(resultUrl);
                        task.updateStatus(ImageToVideoTask.TaskStatus.COMPLETED);
                        task.updateProgress(100);
                        task.setCompletedAt(java.time.LocalDateTime.now());
                        imageToVideoTaskRepository.save(task);
                        logger.info("ImageToVideoTask已更新为COMPLETED: taskId={}", taskId);
                    } else if ("FAILED".equals(status) || "CANCELLED".equals(status)) {
                        task.updateStatus(ImageToVideoTask.TaskStatus.FAILED);
                        task.setErrorMessage(errorMessage);
                        task.setCompletedAt(java.time.LocalDateTime.now());
                        imageToVideoTaskRepository.save(task);
                        logger.info("ImageToVideoTask已更新为FAILED: taskId={}", taskId);
                    } else if ("PROCESSING".equals(status)) {
                        // 处理中状态，更新resultUrl以显示进度
                        if (resultUrl != null && !resultUrl.isEmpty()) {
                            task.setResultUrl(resultUrl);
                            imageToVideoTaskRepository.save(task);
                        }
                    }
                } else {
                    logger.warn("ImageToVideoTask不存在: taskId={}", taskId);
                }
            } else if (taskQueue.getTaskType() == CommonTaskType.STORYBOARD_VIDEO) {
                Optional<StoryboardVideoTask> taskOpt = storyboardVideoTaskRepository.findByTaskId(taskId);
                if (taskOpt.isPresent()) {
                    StoryboardVideoTask task = taskOpt.get();
                    if ("COMPLETED".equals(status)) {
                        // 保存视频URL到videoUrls字段（JSON数组格式）
                        if (resultUrl != null && !resultUrl.isEmpty() && !resultUrl.startsWith("data:image")) {
                            // 视频URL，保存到videoUrls
                            try {
                                ObjectMapper mapper = new ObjectMapper();
                                String videoUrlsJson = mapper.writeValueAsString(java.util.List.of(resultUrl));
                                task.setVideoUrls(videoUrlsJson);
                                logger.info("已设置videoUrls: taskId={}, videoUrls={}", taskId, videoUrlsJson);
                            } catch (Exception e) {
                                // 如果JSON序列化失败，直接保存原始URL
                                task.setVideoUrls("[\"" + resultUrl + "\"]");
                                logger.warn("JSON序列化失败，使用简单格式: taskId={}", taskId);
                            }
                        }
                        // resultUrl 现在存储的是视频URL（替换之前的分镜图Base64）
                        task.setResultUrl(resultUrl);
                        task.setRealTaskId(taskQueue.getRealTaskId());
                        task.updateStatus(StoryboardVideoTask.TaskStatus.COMPLETED);
                        task.updateProgress(100);
                        task.setCompletedAt(java.time.LocalDateTime.now());
                        storyboardVideoTaskRepository.save(task);
                    } else if ("FAILED".equals(status) || "CANCELLED".equals(status)) {
                        task.updateStatus(StoryboardVideoTask.TaskStatus.FAILED);
                        task.setErrorMessage(errorMessage);
                        task.setCompletedAt(java.time.LocalDateTime.now());
                        storyboardVideoTaskRepository.save(task);
                    } else if ("PROCESSING".equals(status)) {
                        // 处理中状态，更新resultUrl以显示进度
                        // 注意：如果当前 resultUrl 是分镜图（Base64图片），且新的 resultUrl 也是图片，则保留分镜图
                        // 只有当新的 resultUrl 是视频URL（不是 data:image 开头）时，才更新
                        if (resultUrl != null && !resultUrl.isEmpty()) {
                            String currentResultUrl = task.getResultUrl();
                            // 如果当前 resultUrl 是分镜图（Base64），且新的 resultUrl 也是图片，则保留分镜图
                            // 只有当新的 resultUrl 是视频URL时，才更新
                            if (currentResultUrl != null && currentResultUrl.startsWith("data:image")) {
                                // 当前是分镜图，只有当新的是视频URL时才更新
                                if (!resultUrl.startsWith("data:image")) {
                                    task.setResultUrl(resultUrl);
                                    storyboardVideoTaskRepository.save(task);
                                }
                                // 如果新的也是图片，保留原来的分镜图
                } else {
                                // 当前不是分镜图，直接更新
                                task.setResultUrl(resultUrl);
                                storyboardVideoTaskRepository.save(task);
                            }
                        }
                    }
                }
            }
        } catch (Exception e) {
            logger.error("更新原始任务状态失败: {}", taskQueue.getTaskId(), e);
            // 重新抛出异常，让调用方知道状态更新失败
            throw new RuntimeException("更新原始任务状态失败: " + e.getMessage(), e);
        }
    }

    /**
     * 取消任务
     */
    @Transactional
    public boolean cancelTask(String taskId, String username) {
        Optional<TaskQueue> taskOpt = taskQueueRepository.findByUsernameAndTaskId(username, taskId);
        if (!taskOpt.isPresent()) {
            return false;
        }

        TaskQueue taskQueue = taskOpt.get();
        if (taskQueue.canProcess()) {
            taskQueue.updateStatus(TaskQueue.QueueStatus.CANCELLED);
            taskQueue.setErrorMessage("用户取消了任务");
            taskQueueRepository.save(taskQueue);

            // 返还冻结的积分
            // 分镜视频任务需要同时返还 _vid 和 _img 积分
            if (taskQueue.getTaskType() == CommonTaskType.STORYBOARD_VIDEO) {
                try {
                    userService.returnFrozenPoints(taskId + "_vid");
                    logger.info("已返还分镜视频积分: taskId={}_vid", taskId);
                } catch (Exception e) {
                    logger.debug("分镜视频积分返还失败（可能未冻结）: taskId={}_vid", taskId);
                }
                try {
                    userService.returnFrozenPoints(taskId + "_img");
                    logger.info("已返还分镜图积分: taskId={}_img", taskId);
                } catch (Exception e) {
                    logger.debug("分镜图积分返还失败（可能未冻结）: taskId={}_img", taskId);
                }
            } else {
                userService.returnFrozenPoints(taskId);
            }

            // 更新原始任务状态
            updateOriginalTaskStatus(taskQueue, "CANCELLED", null, "用户取消了任务");
            
            return true;
        }
        
        return false;
    }

    /**
     * 获取用户的任务队列（仅待处理和处理中的任务）
     */
    @Transactional(readOnly = true)
    public List<TaskQueue> getUserTaskQueue(String username) {
        return taskQueueRepository.findPendingTasksByUsername(username);
    }
    
    /**
     * 分页获取用户的所有任务队列
     */
    @Transactional(readOnly = true)
    public org.springframework.data.domain.Page<TaskQueue> getUserAllTasks(String username, int page, int size) {
        org.springframework.data.domain.Pageable pageable = org.springframework.data.domain.PageRequest.of(page, size);
        return taskQueueRepository.findByUsernameOrderByCreatedAtDesc(username, pageable);
    }

    /**
     * 获取用户任务队列统计
     */
    @Transactional(readOnly = true)
    public long getUserTaskCount(String username) {
        return taskQueueRepository.countByUsername(username);
    }

    /**
     * 检查任务是否在队列中（用于前端决定是否需要轮询）
     * 只有 PENDING 或 PROCESSING 状态的任务才算在队列中
     */
    @Transactional(readOnly = true)
    public boolean isTaskInQueue(String taskId) {
        Optional<TaskQueue> taskOpt = taskQueueRepository.findByTaskId(taskId);
        if (taskOpt.isEmpty()) {
            return false;
        }
        TaskQueue task = taskOpt.get();
        return task.getStatus() == TaskQueue.QueueStatus.PENDING || 
               task.getStatus() == TaskQueue.QueueStatus.PROCESSING;
    }

    /**
     * 清理过期任务
     */
    @Transactional
    public int cleanupExpiredTasks() {
        LocalDateTime expiredDate = LocalDateTime.now().minusDays(7);
        return taskQueueRepository.deleteExpiredTasks(expiredDate);
    }
    
    /**
     * 从任务队列中移除指定任务
     * 用于分镜图生成完成后移除任务，等待用户点击"生成视频"时重新添加
     */
    @Transactional
    public void removeTaskFromQueue(String taskId) {
        Optional<TaskQueue> taskOpt = taskQueueRepository.findByTaskId(taskId);
        if (taskOpt.isPresent()) {
            taskQueueRepository.delete(taskOpt.get());
            logger.info("任务已从队列中移除: {}", taskId);
        } else {
            logger.debug("任务不在队列中，无需移除: {}", taskId);
        }
    }

    /**
     * 检查并处理队列中超时的任务
     * 超时时间：1小时
     * 超过1小时仍在PENDING或PROCESSING状态的任务将被标记为FAILED
     */
    @Transactional
    public int checkAndHandleQueueTimeoutTasks() {
        try {
            // 计算超时时间点（1小时前）
            LocalDateTime timeoutTime = LocalDateTime.now().minusMinutes(60);
            
            // 查找超时的队列任务
            List<TaskQueue> timeoutTasks = taskQueueRepository.findTimeoutTasksByCreatedTime(timeoutTime);
            
            if (timeoutTasks.isEmpty()) {
                return 0;
            }
            
            logger.warn("发现 {} 个队列超时任务，开始处理", timeoutTasks.size());
            
            int handledCount = 0;
            LocalDateTime now = LocalDateTime.now();
            
            for (TaskQueue task : timeoutTasks) {
                try {
                    // 更新队列状态为失败
                    task.setStatus(TaskQueue.QueueStatus.FAILED);
                    task.setErrorMessage("任务队列超时：任务创建后超过1小时仍未完成");
                    task.setUpdatedAt(now);
                    task.setCompletedAt(now);
                    taskQueueRepository.save(task);
                    
                    // 同时更新对应的任务状态
                    updateRelatedTaskStatus(task.getTaskId(), task.getTaskType());
                    
                    // 返还冻结积分
                    // 分镜视频任务需要同时返还 _vid 和 _img 积分
                    if (task.getTaskType() == CommonTaskType.STORYBOARD_VIDEO) {
                        try {
                            userService.returnFrozenPoints(task.getTaskId() + "_vid");
                            logger.info("已返还分镜视频积分: taskId={}_vid", task.getTaskId());
                        } catch (Exception e) {
                            logger.warn("分镜视频积分返还失败（可能已处理）: taskId={}_vid", task.getTaskId());
                        }
                        try {
                            userService.returnFrozenPoints(task.getTaskId() + "_img");
                            logger.info("已返还分镜图积分: taskId={}_img", task.getTaskId());
                        } catch (Exception e) {
                            logger.warn("分镜图积分返还失败（可能已处理）: taskId={}_img", task.getTaskId());
                        }
                    } else {
                        try {
                            userService.returnFrozenPoints(task.getTaskId());
                        } catch (Exception e) {
                            logger.warn("返还冻结积分失败（可能已处理）: taskId={}", task.getTaskId());
                        }
                    }
                    
                    logger.warn("队列任务超时，已标记为失败: taskId={}, taskType={}, createdAt={}", 
                        task.getTaskId(), task.getTaskType(), task.getCreatedAt());
                    handledCount++;
                } catch (Exception e) {
                    logger.error("处理超时队列任务失败: taskId={}", task.getTaskId(), e);
                }
            }
            
            if (handledCount > 0) {
                logger.info("队列超时任务处理完成，处理数量: {}", handledCount);
            }
            return handledCount;
            
        } catch (Exception e) {
            logger.error("检查队列超时任务失败", e);
            return 0;
        }
    }

    /**
     * 更新关联的具体任务状态（使用默认超时错误信息）
     */
    private void updateRelatedTaskStatus(String taskId, CommonTaskType taskType) {
        updateRelatedTaskStatusWithError(taskId, taskType, "任务队列超时：任务创建后超过1小时仍未完成");
    }

    /**
     * 更新关联的具体任务状态（使用自定义错误信息）
     * 注意：通过 task_status 触发级联更新，如果失败则回退到直接更新
     */
    private void updateRelatedTaskStatusWithError(String taskId, CommonTaskType taskType, String errorMessage) {
        try {
            // 尝试通过 task_status 触发级联更新
            boolean updated = taskStatusPollingService.markTaskFailed(taskId, errorMessage);
            if (updated) {
                logger.info("关联任务状态已通过级联更新为FAILED: taskId={}", taskId);
                return; // 级联成功，不需要回退
            }
            
            // 回退：直接更新业务表
            logger.warn("task_status 记录不存在，回退到直接更新业务表: taskId={}", taskId);
            switch (taskType) {
                case TEXT_TO_VIDEO:
                    textToVideoTaskRepository.findByTaskId(taskId).ifPresent(task -> {
                        task.updateStatus(TextToVideoTask.TaskStatus.FAILED);
                        task.setErrorMessage(errorMessage);
                        task.setCompletedAt(java.time.LocalDateTime.now());
                        textToVideoTaskRepository.save(task);
                    });
                    break;
                case IMAGE_TO_VIDEO:
                    imageToVideoTaskRepository.findByTaskId(taskId).ifPresent(task -> {
                        task.updateStatus(ImageToVideoTask.TaskStatus.FAILED);
                        task.setErrorMessage(errorMessage);
                        task.setCompletedAt(java.time.LocalDateTime.now());
                        imageToVideoTaskRepository.save(task);
                    });
                    break;
                case STORYBOARD_VIDEO:
                    storyboardVideoTaskRepository.findByTaskId(taskId).ifPresent(task -> {
                        task.updateStatus(StoryboardVideoTask.TaskStatus.FAILED);
                        task.setErrorMessage(errorMessage);
                        task.setCompletedAt(java.time.LocalDateTime.now());
                        storyboardVideoTaskRepository.save(task);
                    });
                    break;
                case STORYBOARD_IMAGE:
                    // 分镜图任务使用 StoryboardVideoTask 表存储
                    storyboardVideoTaskRepository.findByTaskId(taskId).ifPresent(task -> {
                        task.updateStatus(StoryboardVideoTask.TaskStatus.FAILED);
                        task.setErrorMessage(errorMessage);
                        task.setCompletedAt(java.time.LocalDateTime.now());
                        storyboardVideoTaskRepository.save(task);
                    });
                    break;
            }
            
            // 回退时也需要更新 UserWork
            try {
                userWorkService.updateWorkStatus(taskId, UserWork.WorkStatus.FAILED);
                logger.info("回退更新UserWork状态为FAILED: taskId={}", taskId);
            } catch (Exception workException) {
                logger.warn("回退更新UserWork状态失败: taskId={}, error={}", taskId, workException.getMessage());
            }
            
            // 回退时记录错误日志
            try {
                String username = null;
                String taskTypeStr = taskType != null ? taskType.name() : "UNKNOWN";
                
                switch (taskType) {
                    case TEXT_TO_VIDEO:
                        username = textToVideoTaskRepository.findByTaskId(taskId)
                            .map(t -> t.getUsername()).orElse(null);
                        break;
                    case IMAGE_TO_VIDEO:
                        username = imageToVideoTaskRepository.findByTaskId(taskId)
                            .map(t -> t.getUsername()).orElse(null);
                        break;
                    case STORYBOARD_VIDEO:
                    case STORYBOARD_IMAGE:
                        username = storyboardVideoTaskRepository.findByTaskId(taskId)
                            .map(t -> t.getUsername()).orElse(null);
                        break;
                }
                
                if (username != null) {
                    userErrorLogService.logErrorAsync(
                        username,
                        com.example.demo.model.UserErrorLog.ErrorType.TASK_PROCESSING_ERROR,
                        errorMessage,
                        "TaskQueueService.updateRelatedTaskStatusWithError",
                        taskId,
                        taskTypeStr
                    );
                    logger.info("回退时已记录错误日志: taskId={}, username={}", taskId, username);
                }
            } catch (Exception logException) {
                logger.warn("回退时记录错误日志失败: taskId={}, error={}", taskId, logException.getMessage());
            }
        } catch (Exception e) {
            logger.warn("更新关联任务状态失败: taskId={}, taskType={}", taskId, taskType, e);
        }
    }
    
    /**
     * 直接处理分镜视频任务（绕过队列消费者）
     * 用于解决队列消费者状态检查问题
     */
    @Transactional(propagation = Propagation.NOT_SUPPORTED)
    public void processStoryboardVideoTaskDirectly(String taskId) {
        try {
            // 获取任务队列记录
            Optional<TaskQueue> taskQueueOpt = taskQueueRepository.findByTaskId(taskId);
            if (!taskQueueOpt.isPresent()) {
                logger.warn("直接处理分镜视频：任务不在队列中: {}", taskId);
                return;
            }
            
            TaskQueue taskQueue = taskQueueOpt.get();
            
            // 防重复检查：如果已经有 realTaskId，说明队列消费者已经处理了
            if (taskQueue.getRealTaskId() != null && !taskQueue.getRealTaskId().isEmpty()) {
                logger.info("任务 {} 已有 realTaskId，跳过直接处理（队列消费者已处理）", taskId);
                return;
            }
            
            // 防重复检查：如果状态是 COMPLETED 或 FAILED，跳过
            if (taskQueue.getStatus() == TaskQueue.QueueStatus.COMPLETED || 
                taskQueue.getStatus() == TaskQueue.QueueStatus.FAILED) {
                logger.info("任务 {} 状态为 {}，跳过直接处理", taskId, taskQueue.getStatus());
                return;
            }
            
            // 队列消费者已禁用对分镜视频的处理，不会有并发问题
            // 直接调用视频生成逻辑
            logger.info("直接处理分镜视频任务: taskId={}", taskId);
            Map<String, Object> apiResponse = processStoryboardVideoTask(taskQueue);
            
            // 提取真实任务ID
            String realTaskId = extractRealTaskId(apiResponse);
            if (realTaskId != null) {
                saveRealTaskId(taskId, realTaskId);
                logger.info("分镜视频任务提交成功: taskId={}, realTaskId={}", taskId, realTaskId);
            } else {
                logger.error("无法提取视频任务ID: taskId={}", taskId);
                markTaskAsFailed(taskId, "API未返回有效的任务ID");
            }
        } catch (Exception e) {
            logger.error("直接处理分镜视频任务失败: taskId={}", taskId, e);
            markTaskAsFailed(taskId, "视频生成失败: " + e.getMessage());
        }
    }
}