import { Test, TestingModule } from '@nestjs/testing';
import { QuizExecutionRouter } from './quiz-execution-router';
import { FeatureFlagService } from '../config/feature-flag.service';
import { QuizGenerationSnapshotBuilder } from '../ai-job/quiz-generation-snapshot-builder';
import { AiJobCreationService } from '../ai-job/ai-job-creation.service';
import { UserAiService } from './user-ai.service';
import * as crypto from 'crypto';

function dto(overrides?: any) {
  return {
    jobType: 'quiz_generation',
    targetType: 'knowledge_base',
    targetId: 'kb-001',
    questionCount: 3,
    questionTypes: ['choice', 'judge'],
    difficultyLevel: 'medium',
    ...overrides,
  };
}

describe('QuizExecutionRouter', () => {
  let router: QuizExecutionRouter;
  let mockFlag: any;
  let mockSnapshot: any;
  let mockCreation: any;
  let mockLegacy: any;

  beforeEach(async () => {
    mockFlag = { isEnabled: jest.fn().mockResolvedValue(true) };
    mockSnapshot = {
      build: jest.fn().mockResolvedValue({
        schemaVersion: 'v1',
        snapshot: { userId: 'u1', targetId: 'kb-001', questionCount: 3 },
      }),
    };
    mockCreation = {
      createJob: jest.fn().mockImplementation(async (input: any) => ({
        id: 'job-' + Math.random().toString(36).slice(2, 8),
        createdAt: new Date(),
        ...input,
      })),
    };
    mockLegacy = { createAnalysisJob: jest.fn() };

    const module: TestingModule = await Test.createTestingModule({
      providers: [
        QuizExecutionRouter,
        { provide: FeatureFlagService, useValue: mockFlag },
        { provide: QuizGenerationSnapshotBuilder, useValue: mockSnapshot },
        { provide: AiJobCreationService, useValue: mockCreation },
        { provide: UserAiService, useValue: mockLegacy },
      ],
    }).compile();

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

  describe('幂等：显式 idempotencyKey', () => {
    it('相同 idempotencyKey → 相同 jobId', async () => {
      const key = 'my-stable-key-001';
      mockCreation.createJob.mockResolvedValue({ id: 'job-stable', createdAt: new Date() });

      const r1 = await router.generateQuiz('u1', dto({ idempotencyKey: key }));
      const r2 = await router.generateQuiz('u1', dto({ idempotencyKey: key }));

      // Same idempotencyKey passed to creation service
      const calls = mockCreation.createJob.mock.calls;
      expect(calls[0][0].idempotencyKey).toBe(`quiz-generation:${key}`);
      expect(calls[1][0].idempotencyKey).toBe(`quiz-generation:${key}`);
    });
  });

  describe('幂等：无 idempotencyKey（内容哈希 + 窗口）', () => {
    it('相同参数 → 相同内容哈希（窗口内）', async () => {
      const r1 = await router.generateQuiz('u1', dto());
      const r2 = await router.generateQuiz('u1', dto());

      // Same content → same idempotencyKey (same window)
      const calls = mockCreation.createJob.mock.calls;
      expect(calls[0][0].idempotencyKey).toBe(calls[1][0].idempotencyKey);
    });

    it('不同参数 → 不同内容哈希', async () => {
      const r1 = await router.generateQuiz('u1', dto({ questionCount: 3 }));
      const r2 = await router.generateQuiz('u1', dto({ questionCount: 5 }));

      const calls = mockCreation.createJob.mock.calls;
      // Different questionCount → different hash
      expect(calls[0][0].idempotencyKey).not.toBe(calls[1][0].idempotencyKey);
    });

    it('questionTypes 排序后相同 → 相同哈希', async () => {
      // Unsorted input should normalize
      await router.generateQuiz('u1', dto({ questionTypes: ['judge', 'choice'] }));
      await router.generateQuiz('u1', dto({ questionTypes: ['choice', 'judge'] }));

      const calls = mockCreation.createJob.mock.calls;
      expect(calls[0][0].idempotencyKey).toBe(calls[1][0].idempotencyKey);
    });
  });

  describe('幂等：不包含 Date.now / random', () => {
    it('idempotencyKey 不含随机值格式', async () => {
      // Run 5 times with same params
      const keys = new Set<string>();
      for (let i = 0; i < 5; i++) {
        const result = await router.generateQuiz('u1', dto());
        const key = mockCreation.createJob.mock.calls[i][0].idempotencyKey;
        keys.add(key);
      }
      // Same params within same window → all identical keys
      expect(keys.size).toBe(1);

      const theKey = [...keys][0];
      // Must match quiz-generation:<sha256-24chars>:w<window>
      expect(theKey).toMatch(/^quiz-generation:[0-9a-f]{24}:w\d+$/);
    });
  });

  describe('主动重新生成', () => {
    it('新 idempotencyKey → 新 Job', async () => {
      const r1 = await router.generateQuiz('u1', dto({ idempotencyKey: 'gen-1' }));
      const r2 = await router.generateQuiz('u1', dto({ idempotencyKey: 'gen-2' }));

      const calls = mockCreation.createJob.mock.calls;
      expect(calls[0][0].idempotencyKey).toBe('quiz-generation:gen-1');
      expect(calls[1][0].idempotencyKey).toBe('quiz-generation:gen-2');
      expect(calls[0][0].idempotencyKey).not.toBe(calls[1][0].idempotencyKey);
    });
  });

  describe('Legacy 兼容', () => {
    it('legacy 模式不经过 Unified', async () => {
      mockFlag.isEnabled.mockResolvedValue(false);
      mockLegacy.createAnalysisJob.mockResolvedValue({ jobId: 'legacy-1' });

      const result = await router.generateQuiz('u1', dto());

      expect(result).toEqual({ jobId: 'legacy-1' });
      expect(mockCreation.createJob).not.toHaveBeenCalled();
    });
  });

  describe('contentKey 确定性', () => {
    it('submissionId 可由 contentKey 验证', async () => {
      // Build expected key manually
      const types = ['choice', 'judge'].sort().join(',');
      const normalized = ['knowledge_base', 'kb-001', '3', types, 'medium'].join('|');
      const expectedHash = crypto.createHash('sha256').update(normalized).digest('hex').substring(0, 24);

      await router.generateQuiz('u1', dto());

      const key = mockCreation.createJob.mock.calls[0][0].idempotencyKey;
      expect(key).toContain(expectedHash);
    });
  });
});
