import { Test, TestingModule } from '@nestjs/testing';
import { FeynmanExecutor } from './feynman-executor';
import { FeynmanBusinessValidator, FeynmanReferenceValidator } from './feynman-validator';
import { BusinessValidationError, ReferenceValidationError } from './active-recall-validator';
import { AiGatewayService } from '../ai/gateway/ai-gateway.service';
import { FEYNMAN_JOB_DEFINITION } from './feynman-job-definition';
import type { FeynmanSnapshot } from './feynman-snapshot-builder';

// ═══════════════════════════════════════════════════════════════════════════
// Test helpers
// ═══════════════════════════════════════════════════════════════════════════

function makeSnapshot(overrides: Partial<FeynmanSnapshot['snapshot']> = {}): FeynmanSnapshot {
  return {
    schemaVersion: 'feynman-evaluation-v1',
    snapshot: {
      userId: 'u-001',
      knowledgeItemId: 'ki-001',
      knowledgeItemTitle: '光合作用',
      knowledgeItemContent: '光合作用是植物利用光能将CO2和水转化为有机物并释放氧气的过程。',
      userExplanation: '光合作用就像植物的"做饭"过程，用阳光作为能源。',
      submissionId: 'sub-001',
      knowledgeBaseId: 'kb-001',
      referenceMaterials: [],
      promptKey: 'feynman-evaluation',
      promptVersion: '1.0.0',
      modelTier: 'primary',
      inputSchemaVersion: 'feynman-evaluation-v1',
      outputSchemaVersion: 'feynman-evaluation-v1',
      createdAt: '2026-06-21T10:00:00Z',
      ...overrides,
    },
  };
}

function makeValidOutput() {
  return {
    score: 75,
    clarityLevel: 'mostly_clear' as const,
    summary: '用户用自己的话解释了核心概念，但缺少具体类比帮助理解。',
    strengths: ['用简单语言重述了概念', '抓住了核心要点'],
    weaknesses: ['缺少生活化类比', '部分术语未解释'],
    blindSpots: ['没有说明为什么这个知识点重要'],
    suggestions: ['尝试用一个日常生活中类比来解释', '补充一个具体的使用场景'],
    isBeginnerFriendly: true,
    analogyQuality: 'poor' as const,
    jargonUsage: 'moderate' as const,
  };
}

// ═══════════════════════════════════════════════════════════════════════════
// FeynmanExecutor
// ═══════════════════════════════════════════════════════════════════════════

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

  const mockGatewayResponse = {
    parsed: makeValidOutput(),
    usage: {
      provider: 'deepseek',
      model: 'deepseek-v4-pro',
      inputTokens: 500,
      outputTokens: 300,
      estimatedCost: 0.001,
      latencyMs: 2000,
    },
  };

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

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

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

  describe('execute', () => {
    it('通过 AiGateway 调用模型并返回 parsed 输出', async () => {
      gateway.generate.mockResolvedValue(mockGatewayResponse);

      const snapshot = makeSnapshot();
      const response = await executor.execute(snapshot, 180_000);

      expect(gateway.generate).toHaveBeenCalledTimes(1);
      const callArgs = gateway.generate.mock.calls[0];
      // 第一个参数：GatewayRequest
      expect(callArgs[0].feature).toBe('feynman-evaluation');
      expect(callArgs[0].userId).toBe('u-001');
      expect(callArgs[0].tier).toBe('primary');
      expect(callArgs[0].promptKey).toBe('feynman-evaluation');
      expect(callArgs[0].promptVersion).toBe('1.0.0');
      expect(callArgs[0].messages).toHaveLength(1);
      expect(callArgs[0].messages[0].role).toBe('user');
      // 用户消息应包含知识点标题、原文和解释
      expect(callArgs[0].messages[0].content).toContain('【知识点标题】');
      expect(callArgs[0].messages[0].content).toContain('光合作用');
      expect(callArgs[0].messages[0].content).toContain('【用户的费曼解释】');
      expect(callArgs[0].messages[0].content).toContain('做饭');
      // outputSchema 使用 FeynmanEvaluationResultSchema
      expect(callArgs[0].outputSchema).toBeDefined();
      // 第二个参数：timeoutMs
      expect(callArgs[1]).toBe(180_000);

      expect(response.parsed.score).toBe(75);
      expect(response.usage.inputTokens).toBe(500);
    });

    it('使用 Snapshot 中的 prompt/model 元数据', async () => {
      gateway.generate.mockResolvedValue(mockGatewayResponse);

      const snapshot = makeSnapshot({
        promptKey: 'feynman-evaluation',
        promptVersion: '2.0.0',
        modelTier: 'primary',
      });
      await executor.execute(snapshot, 120_000);

      const callArgs = gateway.generate.mock.calls[0];
      expect(callArgs[0].promptKey).toBe('feynman-evaluation');
      expect(callArgs[0].promptVersion).toBe('2.0.0');
    });

    it('将 timeoutMs 传递给 AiGateway', async () => {
      gateway.generate.mockResolvedValue(mockGatewayResponse);

      await executor.execute(makeSnapshot(), 60_000);
      expect(gateway.generate.mock.calls[0][1]).toBe(60_000);
    });

    it('Executor 无数据库副作用（不直接操作 DB）', async () => {
      // FeynmanExecutor 只依赖 AiGatewayService，无 PrismaService 注入
      gateway.generate.mockResolvedValue(mockGatewayResponse);

      const response = await executor.execute(makeSnapshot(), 180_000);
      expect(response).toBeDefined();
      // 验证 Executor 的构造函数只注入了 AiGatewayService
      // （如果注入了 PrismaService，NestJS DI 会在测试中报错）
    });
  });
});

// ═══════════════════════════════════════════════════════════════════════════
// FeynmanBusinessValidator
// ═══════════════════════════════════════════════════════════════════════════

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

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

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

  describe('正常输出通过', () => {
    it('完整有效输出通过验证', () => {
      const output = makeValidOutput();
      expect(() => validator.validate(output)).not.toThrow();
    });

    it('analogyQuality 为 undefined 时通过（可选字段）', () => {
      const output = { ...makeValidOutput(), analogyQuality: undefined };
      expect(() => validator.validate(output)).not.toThrow();
    });

    it('空数组允许通过', () => {
      const output = {
        ...makeValidOutput(),
        strengths: [],
        weaknesses: [],
        blindSpots: [],
        suggestions: [],
      };
      expect(() => validator.validate(output)).not.toThrow();
    });

    it('score=0 边界通过', () => {
      const output = { ...makeValidOutput(), score: 0 };
      expect(() => validator.validate(output)).not.toThrow();
    });

    it('score=100 边界通过', () => {
      const output = { ...makeValidOutput(), score: 100 };
      expect(() => validator.validate(output)).not.toThrow();
    });
  });

  describe('score 验证', () => {
    it('score 越界（>100）→ BusinessValidationError', () => {
      const output = { ...makeValidOutput(), score: 150 };
      expect(() => validator.validate(output)).toThrow(BusinessValidationError);
      try { validator.validate(output); } catch (e: any) {
        expect(e.violations.some((v: string) => v.includes('out of range'))).toBe(true);
      }
    });

    it('score 越界（<0）→ BusinessValidationError', () => {
      const output = { ...makeValidOutput(), score: -5 };
      expect(() => validator.validate(output)).toThrow(BusinessValidationError);
    });

    it('score 非整数 → BusinessValidationError', () => {
      const output = { ...makeValidOutput(), score: 75.5 };
      expect(() => validator.validate(output)).toThrow(BusinessValidationError);
    });
  });

  describe('clarityLevel 验证', () => {
    it('非法 clarityLevel → BusinessValidationError', () => {
      const output = { ...makeValidOutput(), clarityLevel: 'invalid' as any };
      expect(() => validator.validate(output)).toThrow(BusinessValidationError);
    });
  });

  describe('summary 验证', () => {
    it('空字符串 → BusinessValidationError', () => {
      const output = { ...makeValidOutput(), summary: '' };
      expect(() => validator.validate(output)).toThrow(BusinessValidationError);
    });

    it('纯空格 → BusinessValidationError', () => {
      const output = { ...makeValidOutput(), summary: '   ' };
      expect(() => validator.validate(output)).toThrow(BusinessValidationError);
    });
  });

  describe('数组字段验证', () => {
    it('strengths 中单项 > 500 字符 → BusinessValidationError', () => {
      const output = { ...makeValidOutput(), strengths: ['x'.repeat(501)] };
      expect(() => validator.validate(output)).toThrow(BusinessValidationError);
    });

    it('weaknesses 超过 10 项 → BusinessValidationError', () => {
      const output = {
        ...makeValidOutput(),
        weaknesses: Array.from({ length: 11 }, (_, i) => `weakness ${i}`),
      };
      expect(() => validator.validate(output)).toThrow(BusinessValidationError);
    });

    it('suggestions 含非字符串项 → BusinessValidationError', () => {
      const output = {
        ...makeValidOutput(),
        suggestions: [123 as any],
      };
      expect(() => validator.validate(output)).toThrow(BusinessValidationError);
    });
  });

  describe('布尔/枚举验证', () => {
    it('isBeginnerFriendly 非 boolean → BusinessValidationError', () => {
      const output = { ...makeValidOutput(), isBeginnerFriendly: 'yes' as any };
      expect(() => validator.validate(output)).toThrow(BusinessValidationError);
    });

    it('jargonUsage 非法枚举 → BusinessValidationError', () => {
      const output = { ...makeValidOutput(), jargonUsage: 'extreme' as any };
      expect(() => validator.validate(output)).toThrow(BusinessValidationError);
    });

    it('analogyQuality 非法枚举 → BusinessValidationError', () => {
      const output = { ...makeValidOutput(), analogyQuality: 'terrible' as any };
      expect(() => validator.validate(output)).toThrow(BusinessValidationError);
    });
  });

  describe('空对象 / 模型指令检测', () => {
    it('空对象 {} → BusinessValidationError', () => {
      const output = {} as any;
      expect(() => validator.validate(output)).toThrow(BusinessValidationError);
    });

    it('summary 含代码块 → BusinessValidationError', () => {
      const output = {
        ...makeValidOutput(),
        summary: '```json\n{"score": 75}\n```',
      };
      expect(() => validator.validate(output)).toThrow(BusinessValidationError);
    });

    it('strengths 数组项含代码块 → BusinessValidationError', () => {
      const output = {
        ...makeValidOutput(),
        strengths: ['```json\n{"key": "value"}\n```'],
      };
      expect(() => validator.validate(output)).toThrow(BusinessValidationError);
    });

    it('summary 含模型指令前缀 → BusinessValidationError', () => {
      const output = {
        ...makeValidOutput(),
        summary: 'Here is the evaluation result for this submission',
      };
      expect(() => validator.validate(output)).toThrow(BusinessValidationError);
    });
  });
});

// ═══════════════════════════════════════════════════════════════════════════
// FeynmanReferenceValidator
// ═══════════════════════════════════════════════════════════════════════════

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

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

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

  describe('正常输出通过', () => {
    it('无外部引用的正常输出通过', () => {
      const output = makeValidOutput();
      expect(() => validator.validate(output)).not.toThrow();
    });
  });

  describe('URL 检测', () => {
    it('weakness 包含 URL → ReferenceValidationError', () => {
      const output = {
        ...makeValidOutput(),
        weaknesses: ['查看 https://example.com/leak for details'],
      };
      expect(() => validator.validate(output)).toThrow(ReferenceValidationError);
    });

    it('summary 包含 URL → ReferenceValidationError', () => {
      const output = {
        ...makeValidOutput(),
        summary: 'See https://docs.example.com for more',
      };
      expect(() => validator.validate(output)).toThrow(ReferenceValidationError);
    });
  });

  describe('Email 检测', () => {
    it('suggestion 包含 email → ReferenceValidationError', () => {
      const output = {
        ...makeValidOutput(),
        suggestions: ['Contact user@example.com for help'],
      };
      expect(() => validator.validate(output)).toThrow(ReferenceValidationError);
    });
  });
});
