import { Test, TestingModule } from '@nestjs/testing';
import { ActiveRecallExecutor } from './active-recall-executor';
import { ActiveRecallBusinessValidator, ActiveRecallReferenceValidator, BusinessValidationError, ReferenceValidationError } from './active-recall-validator';
import { AiGatewayService } from '../ai/gateway/ai-gateway.service';
import type { ActiveRecallAnalysisResult } from '../ai/prompts/schemas/active-recall-analysis.schema';

// ═══════════════════════════════════════════════════════════════════════════
// Test fixtures
// ═══════════════════════════════════════════════════════════════════════════

const validSnapshot = {
  schemaVersion: 'active-recall-v1',
  snapshot: {
    userId: 'u-001',
    activeRecallId: 'q-001',
    knowledgeItemId: 'ki-001',
    questionText: '请解释光合作用',
    userAnswer: '光合作用是植物利用光能转化...',
    answerId: 'a-001',
    submittedAt: '2026-06-21T08:30:00.000Z',
    promptKey: 'active-recall-analysis',
    promptVersion: '1.0.0',
    modelTier: 'primary',
    modelProvider: 'deepseek',
    modelName: 'deepseek-v4-pro',
    maxTokens: 4096,
    temperature: 0.3,
  },
};

function validOutput(overrides?: Partial<ActiveRecallAnalysisResult>): ActiveRecallAnalysisResult {
  return {
    score: 72,
    masteryLevel: 'partial',
    summary: '用户理解了核心概念，但缺少具体例子。',
    strengths: ['核心概念理解正确', '能用自己的话表达'],
    weaknesses: ['缺少具体例子', '遗漏了关键应用条件'],
    missingKeyPoints: ['X的应用条件', 'X与Y的关系'],
    misconceptions: [],
    weaknessTypes: ['missing_detail', 'missing_application'],
    focusItems: [
      {
        title: 'X的应用条件',
        reason: '用户在回答中完全未提及应用条件',
        suggestion: '建议回顾知识点第三段',
        priority: 'high' as const,
      },
    ],
    reviewSuggestion: {
      shouldReview: true,
      intervalDays: 2,
      cardFront: '在什么条件下X不能使用？',
      cardBack: '当Y存在时，X失效。',
    },
    ...overrides,
  };
}

// ═══════════════════════════════════════════════════════════════════════════
// ActiveRecallExecutor
// ═══════════════════════════════════════════════════════════════════════════

describe('ActiveRecallExecutor', () => {
  let executor: ActiveRecallExecutor;
  let aiGateway: any;

  beforeEach(async () => {
    aiGateway = {
      generate: jest.fn(),
    };

    const module: TestingModule = await Test.createTestingModule({
      providers: [
        ActiveRecallExecutor,
        { provide: AiGatewayService, useValue: aiGateway },
      ],
    }).compile();

    executor = module.get(ActiveRecallExecutor);
  });

  describe('execute', () => {
    it('正常输出通过所有验证', async () => {
      const mockResponse = {
        parsed: validOutput(),
        usage: { provider: 'deepseek', model: 'deepseek-v4-pro', inputTokens: 500, outputTokens: 800, estimatedCost: 0.001, latencyMs: 1200 },
      };
      aiGateway.generate.mockResolvedValue(mockResponse);

      const result = await executor.execute(validSnapshot, 120000);

      expect(result).toBe(mockResponse);
      expect(aiGateway.generate).toHaveBeenCalledTimes(1);
      // 验证调用参数
      const callArgs = aiGateway.generate.mock.calls[0];
      expect(callArgs[0].feature).toBe('active-recall-analysis');
      expect(callArgs[0].userId).toBe('u-001');
      expect(callArgs[0].promptKey).toBe('active-recall-analysis');
      expect(callArgs[0].promptVersion).toBe('1.0.0');
      expect(callArgs[0].tier).toBe('primary');
      expect(callArgs[0].maxTokens).toBe(4096);
      expect(callArgs[0].messages[0].content).toContain('【用户的主动回忆回答】');
      expect(callArgs[0].messages[0].content).toContain('光合作用是植物利用光能转化');
      expect(callArgs[0].outputSchema).toBeDefined();
      expect(callArgs[1]).toBe(120000); // timeoutMs
    });

    it('所有模型调用经过 AiGateway', async () => {
      aiGateway.generate.mockResolvedValue({
        parsed: validOutput(),
        usage: { provider: 'deepseek', model: 'deepseek-v4-pro', inputTokens: 100, outputTokens: 200, estimatedCost: 0, latencyMs: 500 },
      });

      await executor.execute(validSnapshot, 30000);

      expect(aiGateway.generate).toHaveBeenCalledTimes(1);
      // 无直接 Provider 调用（只有 AiGateway 单一入口）
    });

    it('Executor 无数据库副作用', async () => {
      aiGateway.generate.mockResolvedValue({
        parsed: validOutput(),
        usage: { provider: 'deepseek', model: 'deepseek-v4-pro', inputTokens: 100, outputTokens: 200, estimatedCost: 0, latencyMs: 500 },
      });

      await executor.execute(validSnapshot, 30000);

      // Executor 只调用 AiGateway，不 import PrismaService
      // 若意外导入 PrismaService，NestJS DI 会报错
    });
  });
});

// ═══════════════════════════════════════════════════════════════════════════
// ActiveRecallBusinessValidator
// ═══════════════════════════════════════════════════════════════════════════

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

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

    validator = module.get(ActiveRecallBusinessValidator);
  });

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

    it('score 边界值通过 (0, 100)', () => {
      expect(() => validator.validate(validOutput({ score: 0 }))).not.toThrow();
      expect(() => validator.validate(validOutput({ score: 100 }))).not.toThrow();
    });

    it('所有 masteryLevel 枚举值通过', () => {
      for (const level of ['excellent', 'good', 'partial', 'weak', 'none'] as const) {
        expect(() =>
          validator.validate(validOutput({ masteryLevel: level })),
        ).not.toThrow();
      }
    });

    it('空数组通过', () => {
      expect(() =>
        validator.validate(
          validOutput({ strengths: [], weaknesses: [], missingKeyPoints: [], misconceptions: [], focusItems: [], weaknessTypes: [] }),
        ),
      ).not.toThrow();
    });
  });

  describe('非法输入被拒绝', () => {
    it('score 非整数被拒绝', () => {
      expect(() =>
        validator.validate(validOutput({ score: 72.5 as any })),
      ).toThrow(BusinessValidationError);
    });

    it('score 超出范围被拒绝', () => {
      expect(() => validator.validate(validOutput({ score: -1 }))).toThrow(BusinessValidationError);
      expect(() => validator.validate(validOutput({ score: 101 }))).toThrow(BusinessValidationError);
    });

    it('非法 masteryLevel 被拒绝', () => {
      expect(() =>
        validator.validate(validOutput({ masteryLevel: 'perfect' as any })),
      ).toThrow(BusinessValidationError);
    });

    it('summary 为空被拒绝', () => {
      expect(() => validator.validate(validOutput({ summary: '' }))).toThrow(BusinessValidationError);
    });

    it('summary 超长被拒绝', () => {
      expect(() =>
        validator.validate(validOutput({ summary: 'x'.repeat(2001) })),
      ).toThrow(BusinessValidationError);
    });

    it('strengths 超 10 项被拒绝', () => {
      const arr = Array(11).fill('valid string');
      expect(() => validator.validate(validOutput({ strengths: arr }))).toThrow(BusinessValidationError);
    });

    it('strengths 单个元素超长被拒绝', () => {
      expect(() =>
        validator.validate(validOutput({ strengths: ['x'.repeat(501)] })),
      ).toThrow(BusinessValidationError);
    });

    it('weaknesses 超 10 项被拒绝', () => {
      expect(() =>
        validator.validate(validOutput({ weaknesses: Array(11).fill('valid') })),
      ).toThrow(BusinessValidationError);
    });

    it('missingKeyPoints 超 20 项被拒绝', () => {
      expect(() =>
        validator.validate(validOutput({ missingKeyPoints: Array(21).fill('valid') })),
      ).toThrow(BusinessValidationError);
    });

    it('非法 weaknessTypes 被拒绝', () => {
      expect(() =>
        validator.validate(validOutput({ weaknessTypes: ['invalid_type'] as any })),
      ).toThrow(BusinessValidationError);
    });

    it('focusItems 超 10 项被拒绝', () => {
      const items = Array(11).fill({ title: 't', reason: 'r', priority: 'normal' as const });
      expect(() => validator.validate(validOutput({ focusItems: items }))).toThrow(BusinessValidationError);
    });

    it('focusItem title 为空被拒绝', () => {
      expect(() =>
        validator.validate(
          validOutput({
            focusItems: [{ title: '', reason: 'valid reason', priority: 'normal' }],
          }),
        ),
      ).toThrow(BusinessValidationError);
    });

    it('focusItem reason 为空被拒绝', () => {
      expect(() =>
        validator.validate(
          validOutput({
            focusItems: [{ title: 'valid', reason: '', priority: 'normal' }],
          }),
        ),
      ).toThrow(BusinessValidationError);
    });

    it('focusItem 非法 priority 被拒绝', () => {
      expect(() =>
        validator.validate(
          validOutput({
            focusItems: [{ title: 't', reason: 'r', priority: 'urgent' as any }],
          }),
        ),
      ).toThrow(BusinessValidationError);
    });

    it('reviewSuggestion 缺失被拒绝', () => {
      const output = validOutput();
      delete (output as any).reviewSuggestion;
      expect(() => validator.validate(output)).toThrow(BusinessValidationError);
    });

    it('reviewSuggestion.intervalDays 超出范围被拒绝', () => {
      expect(() =>
        validator.validate(validOutput({ reviewSuggestion: { shouldReview: true, intervalDays: 0 } as any })),
      ).toThrow(BusinessValidationError);
      expect(() =>
        validator.validate(validOutput({ reviewSuggestion: { shouldReview: true, intervalDays: 366 } as any })),
      ).toThrow(BusinessValidationError);
    });

    it('空对象冒充成功结果被拒绝（score 非数字）', () => {
      expect(() => validator.validate({} as any)).toThrow(BusinessValidationError);
    });
  });

  describe('违反信息', () => {
    it('BusinessValidationError 包含 violations 数组', () => {
      try {
        validator.validate(validOutput({ score: 999 }));
        fail('Expected error');
      } catch (err: any) {
        expect(err).toBeInstanceOf(BusinessValidationError);
        expect(err.violations).toBeInstanceOf(Array);
        expect(err.violations.length).toBeGreaterThan(0);
        expect(err.violations[0]).toContain('score');
      }
    });
  });
});

// ═══════════════════════════════════════════════════════════════════════════
// ActiveRecallReferenceValidator
// ═══════════════════════════════════════════════════════════════════════════

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

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

    validator = module.get(ActiveRecallReferenceValidator);
  });

  describe('正常输出通过', () => {
    it('合法输出无引用违规', () => {
      expect(() =>
        validator.validate(validOutput(), { userId: 'u-001', activeRecallId: 'q-001' }),
      ).not.toThrow();
    });
  });

  describe('引用违规被拒绝', () => {
    it('输出含 URL 被拒绝', () => {
      expect(() =>
        validator.validate(
          validOutput({ strengths: ['https://evil.com/leak'] }),
          { userId: 'u-001', activeRecallId: 'q-001' },
        ),
      ).toThrow(ReferenceValidationError);
    });

    it('输出含 email 被拒绝', () => {
      expect(() =>
        validator.validate(
          validOutput({ weaknesses: ['user@example.com data'] }),
          { userId: 'u-001', activeRecallId: 'q-001' },
        ),
      ).toThrow(ReferenceValidationError);
    });

    it('summary 含 URL 被拒绝', () => {
      expect(() =>
        validator.validate(
          validOutput({ summary: 'see https://example.com for details' }),
          { userId: 'u-001', activeRecallId: 'q-001' },
        ),
      ).toThrow(ReferenceValidationError);
    });

    it('focusItem title 含 URL 被拒绝', () => {
      expect(() =>
        validator.validate(
          validOutput({
            focusItems: [{ title: 'http://evil.com', reason: 'r', priority: 'high' }],
          }),
          { userId: 'u-001', activeRecallId: 'q-001' },
        ),
      ).toThrow(ReferenceValidationError);
    });
  });

  describe('ReferenceValidationError 结构', () => {
    it('包含 violations 数组', () => {
      try {
        validator.validate(
          validOutput({ summary: 'see http://leak.com' }),
          { userId: 'u-001', activeRecallId: 'q-001' },
        );
        fail('Expected error');
      } catch (err: any) {
        expect(err).toBeInstanceOf(ReferenceValidationError);
        expect(err.violations).toBeInstanceOf(Array);
        expect(err.violations.length).toBeGreaterThan(0);
      }
    });
  });
});
