import { Test, TestingModule } from '@nestjs/testing';
import { NotFoundException, ForbiddenException, BadRequestException } from '@nestjs/common';
import { JobDefinitionRegistry, DuplicateJobTypeError } from './job-definition-registry';
import { QuizGenerationRegistrationService } from './quiz-generation-registration.service';
import { QUIZ_GENERATION_JOB_DEFINITION } from './quiz-generation-job-definition';
import { QuizGenerationSnapshotBuilder } from './quiz-generation-snapshot-builder';
import { PrismaService } from '../../infrastructure/database/prisma.service';

// ═══════════════════════════════════════════════════════════════════════════
// QuizGenerationRegistrationService
// ═══════════════════════════════════════════════════════════════════════════

describe('QuizGenerationRegistrationService', () => {
  let registry: JobDefinitionRegistry;

  beforeEach(async () => {
    const module: TestingModule = await Test.createTestingModule({
      providers: [JobDefinitionRegistry, QuizGenerationRegistrationService],
    }).compile();
    registry = module.get(JobDefinitionRegistry);
  });

  describe('Definition 注册', () => {
    it('Registry 注册成功', async () => {
      const module = await Test.createTestingModule({
        providers: [JobDefinitionRegistry, QuizGenerationRegistrationService],
      }).compile();
      await module.init();

      const reg = module.get(JobDefinitionRegistry);
      const def = reg.get('quiz_generation');
      expect(def).toBeDefined();
      expect(def.jobType).toBe('quiz_generation');
      expect(def.queue.queueName).toBe('ai-interactive');
      expect(def.metadata.domain).toBe('generation');
    });

    it('重复注册失败', async () => {
      const module = await Test.createTestingModule({
        providers: [JobDefinitionRegistry, QuizGenerationRegistrationService],
      }).compile();
      await module.init();
      const reg = module.get(JobDefinitionRegistry);
      expect(() => reg.register(QUIZ_GENERATION_JOB_DEFINITION)).toThrow(DuplicateJobTypeError);
    });
  });

  describe('Definition 字段验证', () => {
    it('jobType 格式合法', () => {
      expect(QUIZ_GENERATION_JOB_DEFINITION.jobType).toMatch(/^[a-z][a-z0-9_]{1,63}$/);
    });
    it('queueName = ai-interactive', () => {
      expect(QUIZ_GENERATION_JOB_DEFINITION.queue.queueName).toBe('ai-interactive');
    });
    it('modelTier = primary', () => {
      expect(QUIZ_GENERATION_JOB_DEFINITION.model.modelTier).toBe('primary');
    });
    it('promptKey = quiz-generation', () => {
      expect(QUIZ_GENERATION_JOB_DEFINITION.prompt.promptKey).toBe('quiz-generation');
    });
    it('timeoutMs in range', () => {
      expect(QUIZ_GENERATION_JOB_DEFINITION.execution.timeoutMs).toBeGreaterThanOrEqual(1000);
      expect(QUIZ_GENERATION_JOB_DEFINITION.execution.timeoutMs).toBeLessThanOrEqual(600000);
    });
    it('projectorKey set', () => {
      expect(QUIZ_GENERATION_JOB_DEFINITION.projectorKey).toBe('quiz_generation_projector');
    });
  });
});

// ═══════════════════════════════════════════════════════════════════════════
// QuizGenerationSnapshotBuilder
// ═══════════════════════════════════════════════════════════════════════════

describe('QuizGenerationSnapshotBuilder', () => {
  let builder: QuizGenerationSnapshotBuilder;
  let prisma: any;
  let registry: any;

  const mockKb = {
    id: 'kb-001',
    userId: 'u-001',
    title: 'Test KB',
    description: 'A test knowledge base',
    deletedAt: null,
    status: 'active',
  };

  const mockItems = [
    { id: 'ki-1', title: 'Item 1', content: 'Content of item 1', summary: 'Summary 1' },
    { id: 'ki-2', title: 'Item 2', content: 'Content of item 2', summary: 'Summary 2' },
    { id: 'ki-3', title: 'Item 3', content: 'C'.repeat(2500), summary: 'Summary 3' },
  ];

  beforeEach(async () => {
    prisma = {
      knowledgeBase: { findUnique: jest.fn() },
      knowledgeItem: { findMany: jest.fn() },
    };
    registry = { get: jest.fn().mockReturnValue(QUIZ_GENERATION_JOB_DEFINITION) };

    const module: TestingModule = await Test.createTestingModule({
      providers: [
        QuizGenerationSnapshotBuilder,
        { provide: PrismaService, useValue: prisma },
        { provide: JobDefinitionRegistry, useValue: registry },
      ],
    }).compile();

    builder = module.get(QuizGenerationSnapshotBuilder);
    jest.spyOn(require('@nestjs/common').Logger.prototype, 'log').mockImplementation(() => {});
  });

  describe('build', () => {
    it('构建有效快照', async () => {
      prisma.knowledgeBase.findUnique.mockResolvedValue(mockKb);
      prisma.knowledgeItem.findMany.mockResolvedValue(mockItems);

      const snapshot = await builder.build({
        userId: 'u-001',
        targetType: 'knowledge_base',
        targetId: 'kb-001',
        submissionId: 'sub-001',
        questionCount: 3,
        questionTypes: ['choice', 'judge'],
        difficultyLevel: 'medium',
      });

      expect(snapshot.schemaVersion).toBe('quiz-generation-v1');
      expect(snapshot.snapshot.userId).toBe('u-001');
      expect(snapshot.snapshot.questionCount).toBe(3);
      expect(snapshot.snapshot.questionTypes).toEqual(['choice', 'judge']);
      expect(snapshot.snapshot.difficultyLevel).toBe('medium');
      expect(snapshot.snapshot.knowledgeItems).toHaveLength(3);
      expect(snapshot.snapshot.promptKey).toBe('quiz-generation');
      expect(snapshot.snapshot.modelTier).toBe('primary');
    });

    it('prompt/model 值来自 Definition（单一事实来源）', async () => {
      prisma.knowledgeBase.findUnique.mockResolvedValue(mockKb);
      prisma.knowledgeItem.findMany.mockResolvedValue([mockItems[0]]);
      registry.get.mockReturnValue({
        ...QUIZ_GENERATION_JOB_DEFINITION,
        prompt: { promptKey: 'custom-quiz', promptVersion: '2.0' },
      });

      const snapshot = await builder.build({
        userId: 'u-001',
        targetType: 'knowledge_base',
        targetId: 'kb-001',
        submissionId: 'sub-001',
      });

      expect(snapshot.snapshot.promptKey).toBe('custom-quiz');
      expect(snapshot.snapshot.promptVersion).toBe('2.0');
    });

    it('默认值：questionCount=5, types=全类型, difficulty=medium', async () => {
      prisma.knowledgeBase.findUnique.mockResolvedValue(mockKb);
      prisma.knowledgeItem.findMany.mockResolvedValue([mockItems[0]]);

      const snapshot = await builder.build({
        userId: 'u-001',
        targetType: 'knowledge_base',
        targetId: 'kb-001',
        submissionId: 'sub-001',
      });

      expect(snapshot.snapshot.questionCount).toBe(5);
      expect(snapshot.snapshot.questionTypes).toEqual(['choice', 'fill', 'judge']);
      expect(snapshot.snapshot.difficultyLevel).toBe('medium');
    });

    it('content 截断到 2000 字符', async () => {
      prisma.knowledgeBase.findUnique.mockResolvedValue(mockKb);
      prisma.knowledgeItem.findMany.mockResolvedValue([mockItems[2]]); // 2500 chars

      const snapshot = await builder.build({
        userId: 'u-001',
        targetType: 'knowledge_base',
        targetId: 'kb-001',
        submissionId: 'sub-001',
      });

      expect(snapshot.snapshot.knowledgeItems[0].content.length).toBeLessThanOrEqual(2000);
    });

    it('knowledgeBase 不存在 → NotFoundException', async () => {
      prisma.knowledgeBase.findUnique.mockResolvedValue(null);
      await expect(
        builder.build({ userId: 'u-001', targetType: 'knowledge_base', targetId: 'kb-missing', submissionId: 'sub-001' }),
      ).rejects.toThrow(NotFoundException);
    });

    it('knowledgeBase 已删除 → NotFoundException', async () => {
      prisma.knowledgeBase.findUnique.mockResolvedValue({ ...mockKb, deletedAt: new Date() });
      await expect(
        builder.build({ userId: 'u-001', targetType: 'knowledge_base', targetId: 'kb-001', submissionId: 'sub-001' }),
      ).rejects.toThrow(NotFoundException);
    });

    it('跨用户拒绝 → ForbiddenException', async () => {
      prisma.knowledgeBase.findUnique.mockResolvedValue({ ...mockKb, userId: 'u-other' });
      await expect(
        builder.build({ userId: 'u-001', targetType: 'knowledge_base', targetId: 'kb-001', submissionId: 'sub-001' }),
      ).rejects.toThrow(ForbiddenException);
    });

    it('非 knowledge_base targetType → BadRequestException', async () => {
      await expect(
        builder.build({ userId: 'u-001', targetType: 'knowledge_item', targetId: 'ki-1', submissionId: 'sub-001' }),
      ).rejects.toThrow(BadRequestException);
    });

    it('非法 questionCount → BadRequestException', async () => {
      await expect(
        builder.build({ userId: 'u-001', targetType: 'knowledge_base', targetId: 'kb-001', submissionId: 'sub-001', questionCount: 0 }),
      ).rejects.toThrow(BadRequestException);
      await expect(
        builder.build({ userId: 'u-001', targetType: 'knowledge_base', targetId: 'kb-001', submissionId: 'sub-001', questionCount: 51 }),
      ).rejects.toThrow(BadRequestException);
    });

    it('非法 questionTypes → BadRequestException', async () => {
      await expect(
        builder.build({ userId: 'u-001', targetType: 'knowledge_base', targetId: 'kb-001', submissionId: 'sub-001', questionTypes: ['essay' as any] }),
      ).rejects.toThrow(BadRequestException);
    });

    it('非法 difficultyLevel → BadRequestException', async () => {
      await expect(
        builder.build({ userId: 'u-001', targetType: 'knowledge_base', targetId: 'kb-001', submissionId: 'sub-001', difficultyLevel: 'extreme' }),
      ).rejects.toThrow(BadRequestException);
    });

    it('空知识库 → BadRequestException', async () => {
      prisma.knowledgeBase.findUnique.mockResolvedValue(mockKb);
      prisma.knowledgeItem.findMany.mockResolvedValue([]);
      await expect(
        builder.build({ userId: 'u-001', targetType: 'knowledge_base', targetId: 'kb-001', submissionId: 'sub-001' }),
      ).rejects.toThrow(BadRequestException);
    });

    it('Snapshot 不含敏感字段', async () => {
      prisma.knowledgeBase.findUnique.mockResolvedValue(mockKb);
      prisma.knowledgeItem.findMany.mockResolvedValue([mockItems[0]]);

      const snapshot = await builder.build({
        userId: 'u-001', targetType: 'knowledge_base', targetId: 'kb-001', submissionId: 'sub-001',
      });

      const serialized = JSON.stringify(snapshot);
      expect(serialized).not.toContain('Authorization');
      expect(serialized).not.toContain('JWT');
      expect(serialized).not.toContain('apiKey');
      expect(serialized).not.toContain('api_key');
      expect(serialized).not.toContain('password');
      expect(serialized).not.toContain('DATABASE_URL');
    });
  });

  describe('computeHash', () => {
    it('相同输入 → 相同 hash', async () => {
      prisma.knowledgeBase.findUnique.mockResolvedValue(mockKb);
      prisma.knowledgeItem.findMany.mockResolvedValue([mockItems[0]]);

      const s1 = await builder.build({ userId: 'u-001', targetType: 'knowledge_base', targetId: 'kb-001', submissionId: 'sub-001' });
      const s2 = await builder.build({ userId: 'u-001', targetType: 'knowledge_base', targetId: 'kb-001', submissionId: 'sub-001' });

      expect(builder.computeHash(s1)).toBe(builder.computeHash(s2));
    });

    it('不同输入 → 不同 hash', async () => {
      prisma.knowledgeBase.findUnique.mockResolvedValue(mockKb);
      prisma.knowledgeItem.findMany.mockResolvedValueOnce([mockItems[0]]);
      const s1 = await builder.build({ userId: 'u-001', targetType: 'knowledge_base', targetId: 'kb-001', submissionId: 'sub-001' });

      prisma.knowledgeItem.findMany.mockResolvedValueOnce([mockItems[0], mockItems[1]]);
      const s2 = await builder.build({ userId: 'u-001', targetType: 'knowledge_base', targetId: 'kb-001', submissionId: 'sub-002' });

      expect(builder.computeHash(s1)).not.toBe(builder.computeHash(s2));
    });

    it('hash 长度 16，hex 格式', async () => {
      prisma.knowledgeBase.findUnique.mockResolvedValue(mockKb);
      prisma.knowledgeItem.findMany.mockResolvedValue([mockItems[0]]);
      const snapshot = await builder.build({ userId: 'u-001', targetType: 'knowledge_base', targetId: 'kb-001', submissionId: 'sub-001' });

      const hash = builder.computeHash(snapshot);
      expect(hash).toHaveLength(16);
      expect(hash).toMatch(/^[0-9a-f]{16}$/);
    });
  });
});
