/**
 * 通用数据库队列处理器
 *
 * 不使用 Redis/Bull，完全基于 MySQL 数据库队列。
 * 通过 taskType 字段区分不同类型的任务（tts / content），各跑各的处理器。
 *
 * 设计原则：
 *  1. 按类型分离：不同 taskType 独立处理器，互不干扰
 *  2. 原子认领：事务 UPDATE ... WHERE status='pending' 防竞态
 *  3. 超时恢复：定时回收卡住的 processing 任务
 *  4. 内容去重：相同 contentHash + taskType 的任务自动跳过
 */

import { prisma } from '../../models';
import { bookStore } from './book-generator.store';
import { regenerateChapter } from './stage-manager';

// ============ 类型定义 ============

type TaskHandler = (taskId: number) => Promise<void>;

interface QueueConfig {
  /** 队列名称（用于日志） */
  name: string;
  /** 任务类型（对应 TtsTask.taskType 字段） */
  taskType: string;
  /** 轮询间隔（毫秒），默认 3000 */
  pollIntervalMs?: number;
  /** 最大并发数，默认 1 */
  maxConcurrency?: number;
  /** 任务超时时间（毫秒），默认 10 分钟 */
  processingTimeoutMs?: number;
  /** 任务处理函数 */
  handler: TaskHandler;
}

// ============ 队列工厂 ============

interface QueueInstance {
  start: () => void;
  stop: () => void;
  getStats: () => Promise<{ pending: number; processing: number; completed: number; failed: number }>;
}

function createGenQueue(config: QueueConfig): QueueInstance {
  const {
    name,
    taskType,
    pollIntervalMs = 3000,
    maxConcurrency = 1,
    processingTimeoutMs = 10 * 60 * 1000,
    handler,
  } = config;

  let isRunning = false;
  let timer: ReturnType<typeof setInterval> | null = null;
  let activeCount = 0;
  let lastStaleRecoveryTime = 0;

  // 公开 API
  function start(): void {
    if (isRunning) {
      console.warn(`[${name}] 队列已在运行，跳过重复启动`);
      return;
    }
    isRunning = true;
    console.log(`[${name}] 数据库队列已启动 (taskType=${taskType}, 并发=${maxConcurrency}, 轮询=${pollIntervalMs}ms)`);

    recoverStaleTasks().then(async () => {
      // 启动时清理已完成书籍的队列历史记录
      await cleanupDoneBooks();
      timer = setInterval(pollTick, pollIntervalMs);
      pollTick();
    });
  }

  function stop(): void {
    if (!isRunning) return;
    isRunning = false;
    if (timer) { clearInterval(timer); timer = null; }
    console.log(`[${name}] 队列已停止`);
  }

  async function getStats() {
    const where = { taskType };
    const [pending, processing, completed, failed] = await Promise.all([
      prisma.ttsTask.count({ where: { ...where, status: 'pending' } }),
      prisma.ttsTask.count({ where: { ...where, status: 'processing' } }),
      prisma.ttsTask.count({ where: { ...where, status: 'completed' } }),
      prisma.ttsTask.count({ where: { ...where, status: 'failed' } }),
    ]);
    return { pending, processing, completed, failed };
  }

  // 内部
  async function pollTick(): Promise<void> {
    if (!isRunning) return;
    try {
      if (activeCount === 0) await recoverStaleTasksIfNeeded();
      if (activeCount >= maxConcurrency) return;

      const task = await claimPendingTask();
      if (!task) return;

      activeCount++;
      console.log(`[${name}] 认领任务 #${task.id}, chapterId=${task.chapterId}`);

      processTask(task).finally(() => { activeCount--; });
    } catch (err: any) {
      console.error(`[${name}] 轮询异常:`, err.message);
    }
  }

  async function claimPendingTask() {
    return prisma.$transaction(async (tx) => {
      const task = await tx.ttsTask.findFirst({
        where: { taskType, status: 'pending' },
        orderBy: { createdAt: 'asc' },
      });
      if (!task) return null;

      await tx.ttsTask.update({
        where: { id: task.id },
        data: { status: 'processing', startedAt: new Date() },
      });
      return task;
    });
  }

  async function processTask(task: any): Promise<void> {
    try {
      await handler(task.id);
    } catch (err: any) {
      console.error(`[${name}] 任务 #${task.id} 处理异常:`, err.message);
      try {
        await prisma.ttsTask.update({
          where: { id: task.id },
          data: { status: 'failed', errorMsg: err.message || '未知错误', completedAt: new Date() },
        });
      } catch (updateErr) {
        console.error(`[${name}] 更新失败状态异常:`, updateErr);
      }
    }
  }

  // 僵尸任务恢复
  async function recoverStaleTasksIfNeeded(): Promise<void> {
    const now = Date.now();
    if (now - lastStaleRecoveryTime < 30_000) return;
    lastStaleRecoveryTime = now;
    await recoverStaleTasks();
  }

  async function recoverStaleTasks(): Promise<void> {
    const staleTime = new Date(Date.now() - processingTimeoutMs);
    const MAX_ZOMBIE_RETRIES = 5;
    try {
      // 1. 回收未超重试上限的僵尸任务
      const recovered = await prisma.ttsTask.updateMany({
        where: { taskType, status: 'processing', startedAt: { lt: staleTime }, retryCount: { lt: MAX_ZOMBIE_RETRIES } },
        data: { status: 'pending', retryCount: { increment: 1 }, errorMsg: '任务超时，重新排队', startedAt: null },
      });
      if (recovered.count > 0) {
        console.log(`[${name}] 回收了 ${recovered.count} 个僵尸任务`);
      }

      // 2. 超过重试上限的僵尸任务 → 直接标记失败并回退章节
      const overLimit = await prisma.ttsTask.findMany({
        where: { taskType, status: 'processing', startedAt: { lt: staleTime }, retryCount: { gte: MAX_ZOMBIE_RETRIES } },
        select: { id: true, chapterId: true },
      });
      for (const t of overLimit) {
        await prisma.ttsTask.update({
          where: { id: t.id },
          data: { status: 'failed', errorMsg: `僵尸任务重试超限(${MAX_ZOMBIE_RETRIES}次)，强制失败`, completedAt: new Date() },
        });
        if (t.chapterId) {
          await regenerateChapter(t.chapterId, 'content_completed').catch(() => {});
        }
      }
      if (overLimit.length > 0) {
        console.log(`[${name}] 强制终止 ${overLimit.length} 个超限僵尸任务`);
      }
    } catch (err: any) {
      console.error(`[${name}] 僵尸任务回收异常:`, err.message);
    }
  }

  return { start, stop, getStats };
}

// ============ 导出队列实例 ============

/** TTS 音频生成队列（taskType='tts', 并发=3）
 *  并发度从1提升到3，利用 MiniMax 异步长文本和阿里云多并发能力，
 *  一本65个叶节点的书音频生成时间从约33分钟降至约11分钟。
 */
export const ttsQueue = createGenQueue({
  name: 'TtsQueue',
  taskType: 'tts',
  pollIntervalMs: 3000,
  maxConcurrency: 3,
  handler: (taskId: number) => bookStore.processTtsTask(taskId),
});

// ============ 兼容旧导出 ============

export function startTtsQueue(): void { ttsQueue.start(); }
export function stopTtsQueue(): void { ttsQueue.stop(); }

// ============ 启动时清理 ============

let _cleanupRan = false;

/**
 * 清理所有已完成的书籍的队列任务记录
 * 启动时调用一次，避免历史数据堆积
 *
 * 优化：使用聚合查询替代逐书 N+1 查询，一次性找出所有"全部章节已完成"的书籍
 */
async function cleanupDoneBooks(): Promise<void> {
  if (_cleanupRan) return;
  _cleanupRan = true;
  try {
    const doneStages = ['audio_completed', 'video_generating', 'video_completed'];

    // 第一步：按 bookId 聚合，找出有"未完成章节"的书籍（排除它们）
    const notDoneBooks = await prisma.bookChapter.groupBy({
      by: ['bookId'],
      where: {
        genStage: { notIn: doneStages },
        bookId: { not: null },
      },
      _count: { id: true },
    });
    const notDoneBookIds = new Set(notDoneBooks.map(b => b.bookId));

    // 第二步：找出所有有章节的书籍，排除有未完成章节的
    const allBookGroups = await prisma.bookChapter.groupBy({
      by: ['bookId'],
      where: { bookId: { not: null } },
      _count: { id: true },
    });
    const doneBookIds = allBookGroups
      .filter(g => g.bookId && !notDoneBookIds.has(g.bookId))
      .map(g => g.bookId!);

    if (doneBookIds.length === 0) return;

    // 第三步：批量删除已完成书籍的队列任务
    const result = await prisma.ttsTask.deleteMany({
      where: { bookId: { in: doneBookIds } },
    });

    if (result.count > 0) {
      console.log(`[Cleanup] 启动清理：${doneBookIds.length} 本已完成书籍，删除 ${result.count} 条任务记录`);
    }
  } catch (err: any) {
    console.warn(`[Cleanup] 启动清理异常:`, err.message);
  }
}
