import { Test, TestingModule } from '@nestjs/testing';
import { QuizGenerationExecutor } from './quiz-generation-executor';
import { QuizGenerationValidator } from './quiz-generation-validator';
import { AiGatewayService } from '../ai/gateway/ai-gateway.service';
import { BusinessValidationError } from './active-recall-validator';

// ═══════════════════════════════════════════════════════════════
// Helpers
// ═══════════════════════════════════════════════════════════════

function validSnapshot() {
  return {
    schemaVersion: 'quiz-generation-v1',
    snapshot: {
      userId: 'u-001',
      targetType: 'knowledge_base',
      targetId: 'kb-001',
      submissionId: 'sub-001',
      knowledgeBaseTitle: 'Test KB',
      knowledgeBaseDescription: 'A test kb',
      knowledgeItems: [
        { id: 'ki-1', title: 'Item 1', content: 'Content 1', summary: 'S1' },
        { id: 'ki-2', title: 'Item 2', content: 'Content 2', summary: 'S2' },
      ],
      questionCount: 3,
      questionTypes: ['choice', 'judge'],
      difficultyLevel: 'medium',
      knowledgePointIds: [],
      promptKey: 'quiz-generation',
      promptVersion: '1.0.0',
      modelTier: 'primary',
      inputSchemaVersion: 'quiz-generation-v1',
      outputSchemaVersion: 'quiz-generation-v1',
      createdAt: '2026-06-22T10:00:00Z',
    },
  };
}

function validOutput() {
  return {
    quizTitle: 'Test Quiz',
    quizDescription: 'A test quiz',
    questions: [
      { type: 'choice' as const, stem: 'Q1?', options: ['A', 'B', 'C', 'D'], answer: '0', explanation: 'Because A' },
      { type: 'judge' as const, stem: 'Q2?', answer: 'true', explanation: 'Correct' },
      { type: 'fill' as const, stem: 'Q3: ____ is the answer.', answer: '42', explanation: 'The answer is 42' },
    ],
  };
}

// ═══════════════════════════════════════════════════════════════
// QuizGenerationExecutor
// ═══════════════════════════════════════════════════════════════

describe('QuizGenerationExecutor', () => {
  let executor: QuizGenerationExecutor;
  let gateway: any;

  beforeEach(async () => {
    gateway = { generate: jest.fn() };
    const module: TestingModule = await Test.createTestingModule({
      providers: [
        QuizGenerationExecutor,
        { provide: AiGatewayService, useValue: gateway },
      ],
    }).compile();
    executor = module.get(QuizGenerationExecutor);
  });

  describe('execute', () => {
    it('通过 AiGateway 调用并返回结构化输出', async () => {
      gateway.generate.mockResolvedValue({ parsed: validOutput(), usage: { inputTokens: 500, outputTokens: 400 } });
      const result = await executor.execute(validSnapshot(), 180000);
      expect(result.parsed.questions).toHaveLength(3);
      expect(result.parsed.quizTitle).toBe('Test Quiz');
      expect(gateway.generate).toHaveBeenCalledTimes(1);
    });

    it('使用 quiz-generation prompt + primary tier', async () => {
      gateway.generate.mockResolvedValue({ parsed: validOutput(), usage: {} });
      await executor.execute(validSnapshot(), 180000);
      const call = gateway.generate.mock.calls[0][0];
      expect(call.feature).toBe('quiz-generation');
      expect(call.tier).toBe('primary');
      expect(call.promptKey).toBe('quiz-generation');
      expect(call.promptVersion).toBe('1.0.0');
    });

    it('user message 包含知识点内容', async () => {
      gateway.generate.mockResolvedValue({ parsed: validOutput(), usage: {} });
      await executor.execute(validSnapshot(), 180000);
      const msg = gateway.generate.mock.calls[0][0].messages[0].content;
      expect(msg).toContain('Test KB');
      expect(msg).toContain('Item 1');
      expect(msg).toContain('Content 1');
      expect(msg).toContain('生成 3 道测验题目');
      expect(msg).toContain('choice、judge');
      expect(msg).toContain('medium');
    });

    it('传递 maxTokens + timeoutMs', async () => {
      gateway.generate.mockResolvedValue({ parsed: validOutput(), usage: {} });
      await executor.execute(validSnapshot(), 120000);
      expect(gateway.generate.mock.calls[0][0].maxTokens).toBe(4096);
      expect(gateway.generate.mock.calls[1]?.[1]).toBeUndefined(); // timeout is second arg
    });

    it('Provider 失败 → 错误传播', async () => {
      gateway.generate.mockRejectedValue(new Error('AI timeout'));
      await expect(executor.execute(validSnapshot(), 180000)).rejects.toThrow('AI timeout');
    });
  });
});

// ═══════════════════════════════════════════════════════════════
// QuizGenerationValidator
// ═══════════════════════════════════════════════════════════════

describe('QuizGenerationValidator', () => {
  let validator: QuizGenerationValidator;

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

  describe('正常输出', () => {
    it('合法输出验证通过', () => {
      expect(() => validator.validate(validOutput())).not.toThrow();
    });
  });

  describe('结构错误', () => {
    it('空 questions → 拒绝', () => {
      expect(() => validator.validate({ quizTitle: 'T', questions: [] }))
        .toThrow(BusinessValidationError);
    });
    it('questions 非数组 → 拒绝', () => {
      expect(() => validator.validate({ quizTitle: 'T', questions: null as any }))
        .toThrow(BusinessValidationError);
    });
    it('quizTitle 为空 → 拒绝', () => {
      expect(() => validator.validate({ quizTitle: '', questions: [validOutput().questions[0]] }))
        .toThrow(BusinessValidationError);
    });
  });

  describe('题型验证', () => {
    it('非法 type → 拒绝', () => {
      const q = { type: 'essay' as any, stem: 'Q', answer: 'A', explanation: 'E' };
      expect(() => validator.validate({ quizTitle: 'T', questions: [q] }))
        .toThrow(BusinessValidationError);
    });

    it('choice 无 options → 拒绝', () => {
      const q = { type: 'choice' as const, stem: 'Q', answer: '0', explanation: 'E' };
      expect(() => validator.validate({ quizTitle: 'T', questions: [q] }))
        .toThrow(BusinessValidationError);
    });

    it('choice options < 2 → 拒绝', () => {
      const q = { type: 'choice' as const, stem: 'Q', options: ['A'], answer: '0', explanation: 'E' };
      expect(() => validator.validate({ quizTitle: 'T', questions: [q] }))
        .toThrow(BusinessValidationError);
    });

    it('choice answer 不是有效索引 → 拒绝', () => {
      const q = { type: 'choice' as const, stem: 'Q', options: ['A', 'B', 'C'], answer: '5', explanation: 'E' };
      expect(() => validator.validate({ quizTitle: 'T', questions: [q] }))
        .toThrow(BusinessValidationError);
    });

    it('choice 重复选项 → 拒绝', () => {
      const q = { type: 'choice' as const, stem: 'Q', options: ['A', 'A', 'B', 'C'], answer: '0', explanation: 'E' };
      expect(() => validator.validate({ quizTitle: 'T', questions: [q] }))
        .toThrow(BusinessValidationError);
    });

    it('judge answer 非法 → 拒绝', () => {
      const q = { type: 'judge' as const, stem: 'Q', answer: 'yes', explanation: 'E' };
      expect(() => validator.validate({ quizTitle: 'T', questions: [q] }))
        .toThrow(BusinessValidationError);
    });
  });

  describe('字段非空', () => {
    it('stem 空 → 拒绝', () => {
      const q = { type: 'fill' as const, stem: '', answer: 'A', explanation: 'E' };
      expect(() => validator.validate({ quizTitle: 'T', questions: [q] }))
        .toThrow(BusinessValidationError);
    });
    it('answer 空 → 拒绝', () => {
      const q = { type: 'fill' as const, stem: 'Q', answer: '', explanation: 'E' };
      expect(() => validator.validate({ quizTitle: 'T', questions: [q] }))
        .toThrow(BusinessValidationError);
    });
    it('explanation 空 → 拒绝', () => {
      const q = { type: 'fill' as const, stem: 'Q', answer: 'A', explanation: '' };
      expect(() => validator.validate({ quizTitle: 'T', questions: [q] }))
        .toThrow(BusinessValidationError);
    });
  });

  describe('重复题目', () => {
    it('相同 stem → 拒绝', () => {
      const q = { type: 'fill' as const, stem: 'Same Q', answer: 'A', explanation: 'E' };
      expect(() => validator.validate({ quizTitle: 'T', questions: [q, q] }))
        .toThrow(BusinessValidationError);
    });
  });

  describe('模型指令', () => {
    it('stem 含代码块 → 拒绝', () => {
      const q = { type: 'fill' as const, stem: '```json\n{}```', answer: 'A', explanation: 'E' };
      expect(() => validator.validate({ quizTitle: 'T', questions: [q] }))
        .toThrow(BusinessValidationError);
    });
  });
});
