/**
 * Stage Manager 单元测试
 * 测试状态机: safeTransitionChapter, advanceChapter, regenerateChapter
 */

import { describe, it, expect, beforeEach, vi } from 'vitest';
import {
  chapterStageIndex,
  safeTransitionChapter,
  advanceChapter,
  regenerateChapter,
  getCleanupForChapterRegression,
} from '@/modules/book-generator/stage-manager';

// Mock Prisma
const mockUpdateMany = vi.fn();
const mockFindUnique = vi.fn();
const mockFindUniqueOrThrow = vi.fn();

vi.mock('@/models', () => ({
  prisma: {
    bookChapter: {
      updateMany: (...args: any[]) => mockUpdateMany(...args),
      findUnique: (...args: any[]) => mockFindUnique(...args),
      findUniqueOrThrow: (...args: any[]) => mockFindUniqueOrThrow(...args),
    },
  },
}));

describe('chapterStageIndex', () => {
  it('返回正确的阶段索引', () => {
    expect(chapterStageIndex('idle')).toBe(0);
    expect(chapterStageIndex('outline_completed')).toBe(1);
    expect(chapterStageIndex('content_generating')).toBe(2);
    expect(chapterStageIndex('content_completed')).toBe(3);
    expect(chapterStageIndex('audio_generating')).toBe(4);
    expect(chapterStageIndex('audio_completed')).toBe(5);
    expect(chapterStageIndex('video_generating')).toBe(6);
    expect(chapterStageIndex('video_completed')).toBe(7);
    expect(chapterStageIndex('failed')).toBe(8);
  });
});

describe('章节转移矩阵', () => {
  // 本地副本用于测试（因为 CHAPTER_VALID_TRANSITIONS 不是导出的）
  const CHAPTER_VALID_TRANSITIONS: Record<string, string[]> = {
    'idle': ['content_generating', 'failed'],
    'outline_completed': ['content_generating', 'failed'],
    'content_generating': ['content_completed', 'failed'],
    'content_completed': ['audio_generating', 'audio_completed', 'content_generating', 'failed'],
    'audio_generating': ['audio_completed', 'content_completed', 'failed'],
    'audio_completed': ['video_generating', 'audio_generating', 'content_generating', 'failed'],
    'video_generating': ['video_completed', 'failed'],
    'video_completed': ['video_generating', 'audio_generating', 'content_generating', 'failed'],
    'failed': ['content_generating', 'audio_generating', 'video_generating', 'idle'],
  };

  it('idle 可转移到 content_generating 或 failed', () => {
    expect(CHAPTER_VALID_TRANSITIONS['idle']).toContain('content_generating');
    expect(CHAPTER_VALID_TRANSITIONS['idle']).toContain('failed');
  });

  it('content_completed 可转移到多个阶段', () => {
    const allowed = CHAPTER_VALID_TRANSITIONS['content_completed'];
    expect(allowed).toContain('audio_generating');
    expect(allowed).toContain('audio_completed');
    expect(allowed).toContain('content_generating');
    expect(allowed).toContain('failed');
  });

  it('failed 可重新生成', () => {
    const allowed = CHAPTER_VALID_TRANSITIONS['failed'];
    expect(allowed).toContain('content_generating');
    expect(allowed).toContain('audio_generating');
    expect(allowed).toContain('video_generating');
    expect(allowed).toContain('idle');
  });
});

describe('getCleanupForChapterRegression', () => {
  it('回退到 content_generating 时清理音频和视频', () => {
    const clean = getCleanupForChapterRegression('content_generating');
    expect(clean.audioUrl).toBeNull();
    expect(clean.audioDuration).toBe(0);
    expect(clean.videoUrl).toBeNull();
    expect(clean.videoDuration).toBeNull();
  });

  it('回退到 audio_generating 时只清理视频', () => {
    const clean = getCleanupForChapterRegression('audio_generating');
    expect(clean.audioUrl).toBeNull();
    expect(clean.videoUrl).toBeNull();
    expect(clean.videoDuration).toBeNull();
  });

  it('回退到 audio_completed 时保留音频', () => {
    const clean = getCleanupForChapterRegression('audio_completed');
    expect(clean.audioUrl).toBeUndefined();
    expect(clean.videoUrl).toBeNull();
    expect(clean.videoDuration).toBeNull();
  });

  it('保持 video_completed 时不清理任何资源', () => {
    const clean = getCleanupForChapterRegression('video_completed');
    expect(clean.audioUrl).toBeUndefined();
    expect(clean.videoUrl).toBeUndefined();
    expect(clean.videoDuration).toBeUndefined();
  });
});

describe('safeTransitionChapter', () => {
  beforeEach(() => {
    vi.clearAllMocks();
  });

  it('有效转移成功执行', async () => {
    mockUpdateMany.mockResolvedValue({ count: 1 });

    await expect(
      safeTransitionChapter(1, 'content_completed', 'audio_generating')
    ).resolves.toBeUndefined();

    expect(mockUpdateMany).toHaveBeenCalledWith(
      expect.objectContaining({
        where: expect.objectContaining({ id: 1, genStage: 'content_completed' }),
        data: expect.objectContaining({ genStage: 'audio_generating' }),
      })
    );
  });

  it('无效转移抛出错误', async () => {
    mockUpdateMany.mockResolvedValue({ count: 0 });
    mockFindUnique.mockResolvedValue({ genStage: 'content_completed' });

    await expect(
      safeTransitionChapter(1, 'idle', 'audio_generating')
    ).rejects.toThrow(/非法状态转移/);
  });

  it('乐观锁冲突时返回（不抛错）', async () => {
    mockUpdateMany.mockResolvedValue({ count: 0 });
    mockFindUnique.mockResolvedValue({ genStage: 'audio_generating' });

    await expect(
      safeTransitionChapter(1, 'content_completed', 'audio_generating')
    ).resolves.toBeUndefined();
  });

  it('目标阶段与实际阶段相同时跳过', async () => {
    mockUpdateMany.mockResolvedValue({ count: 0 });
    mockFindUnique.mockResolvedValue({ genStage: 'audio_generating' });

    await expect(
      safeTransitionChapter(1, 'content_completed', 'audio_generating')
    ).resolves.toBeUndefined();
  });

  it('failed 状态回退时跳过', async () => {
    mockUpdateMany.mockResolvedValue({ count: 0 });
    mockFindUnique.mockResolvedValue({ genStage: 'failed' });

    await expect(
      safeTransitionChapter(1, 'failed', 'content_generating')
    ).resolves.toBeUndefined();
  });

  it('回退时清理下游资源', async () => {
    mockUpdateMany.mockResolvedValue({ count: 1 });

    await safeTransitionChapter(1, 'audio_completed', 'content_generating');

    expect(mockUpdateMany).toHaveBeenCalledWith(
      expect.objectContaining({
        where: expect.objectContaining({ id: 1, genStage: 'audio_completed' }),
        data: expect.objectContaining({
          genStage: 'content_generating',
          audioUrl: null,
          audioDuration: 0,
          videoUrl: null,
          videoDuration: null,
        }),
      })
    );
  });
});

describe('advanceChapter', () => {
  beforeEach(() => {
    vi.clearAllMocks();
  });

  it('只能前进到更高级阶段', async () => {
    mockFindUniqueOrThrow.mockResolvedValue({ id: 1, genStage: 'content_completed' });
    mockUpdateMany.mockResolvedValue({ count: 1 });

    await expect(
      advanceChapter(1, 'audio_generating')
    ).resolves.toBeUndefined();
  });

  it('不允许回退到低级阶段', async () => {
    mockFindUniqueOrThrow.mockResolvedValue({ id: 1, genStage: 'content_completed' });

    await expect(
      advanceChapter(1, 'content_generating')
    ).rejects.toThrow(/advanceChapter 不能用于回退/);
  });

  it('不允许同级跳转', async () => {
    mockFindUniqueOrThrow.mockResolvedValue({ id: 1, genStage: 'content_completed' });

    await expect(
      advanceChapter(1, 'content_completed')
    ).rejects.toThrow(/advanceChapter 不能用于回退/);
  });
});

describe('regenerateChapter', () => {
  beforeEach(() => {
    vi.clearAllMocks();
  });

  it('允许回退到上游阶段', async () => {
    mockFindUniqueOrThrow.mockResolvedValue({ id: 1, genStage: 'audio_completed' });
    mockUpdateMany.mockResolvedValue({ count: 1 });

    await expect(
      regenerateChapter(1, 'content_generating')
    ).resolves.toBeUndefined();
  });

  it('允许重新生成音频', async () => {
    mockFindUniqueOrThrow.mockResolvedValue({ id: 1, genStage: 'audio_completed' });
    mockUpdateMany.mockResolvedValue({ count: 1 });

    await expect(
      regenerateChapter(1, 'audio_generating')
    ).resolves.toBeUndefined();
  });

  it('允许从 failed 状态重试', async () => {
    mockFindUniqueOrThrow.mockResolvedValue({ id: 1, genStage: 'failed' });
    mockUpdateMany.mockResolvedValue({ count: 1 });

    await expect(
      regenerateChapter(1, 'content_generating')
    ).resolves.toBeUndefined();
  });
});

describe('状态转换边界情况', () => {
  beforeEach(() => {
    vi.clearAllMocks();
  });

  it('video_generating 只能转到 video_completed 或 failed', async () => {
    mockFindUniqueOrThrow.mockResolvedValue({ id: 1, genStage: 'video_generating' });
    mockUpdateMany.mockResolvedValue({ count: 1 });

    // video_generating 不能直接到 audio_completed
    await expect(
      regenerateChapter(1, 'audio_completed')
    ).rejects.toThrow(/非法状态转移/);
  });

  it('video_completed 可回到 audio_generating', async () => {
    mockFindUniqueOrThrow.mockResolvedValue({ id: 1, genStage: 'video_completed' });
    mockUpdateMany.mockResolvedValue({ count: 1 });

    await expect(
      regenerateChapter(1, 'audio_generating')
    ).resolves.toBeUndefined();
  });

  it('连续前进正确执行', async () => {
    mockFindUniqueOrThrow.mockResolvedValue({ id: 1, genStage: 'content_completed' });
    mockUpdateMany.mockResolvedValue({ count: 1 });

    await advanceChapter(1, 'audio_generating');
    expect(mockUpdateMany).toHaveBeenLastCalledWith(
      expect.objectContaining({
        data: expect.objectContaining({ genStage: 'audio_generating' }),
      })
    );

    mockFindUniqueOrThrow.mockResolvedValue({ id: 1, genStage: 'audio_generating' });
    mockUpdateMany.mockResolvedValue({ count: 1 });

    await advanceChapter(1, 'audio_completed');
    expect(mockUpdateMany).toHaveBeenLastCalledWith(
      expect.objectContaining({
        data: expect.objectContaining({ genStage: 'audio_completed' }),
      })
    );
  });
});