import { Test, TestingModule } from '@nestjs/testing';
import { ReviewCardGenerationExecutor } from './review-card-generation-executor';
import { ReviewCardGenerationValidator } from './review-card-generation-validator';
import { ReviewCardGenerationSnapshotBuilder } from './review-card-generation-snapshot-builder';
import { REVIEW_CARD_GENERATION_JOB_DEFINITION } from './review-card-generation-job-definition';
import { JobDefinitionRegistry } from './job-definition-registry';
import { AiGatewayService } from '../ai/gateway/ai-gateway.service';
import { BusinessValidationError } from './active-recall-validator';

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

function validSnapshot() {
  const registry = { get: () => REVIEW_CARD_GENERATION_JOB_DEFINITION };
  const builder = new ReviewCardGenerationSnapshotBuilder(registry as any);
  return builder.build({
    userId: 'u-001',
    parentJobId: 'parent-job-001',
    sourceResultId: 'fe_parent-job-001',
    knowledgeItemId: 'ki-001',
    knowledgeBaseId: 'kb-001',
    summary: '用户对光合作用有基本理解。',
    strengths: ['术语使用正确'],
    weaknesses: ['光反应细节不准确', '暗反应遗漏关键步骤'],
  });
}

function validGatewayResponse(cards: any[] = [
  { frontText: '什么是光合作用的光反应？', backText: '光反应是光合作用的第一阶段...', difficulty: 'medium', tags: ['生物'] },
  { frontText: '暗反应的关键步骤有哪些？', backText: '暗反应包括碳固定、还原和再生三个阶段...', difficulty: 'hard', tags: ['生物'] },
]) {
  return {
    parsed: { cards, totalCount: cards.length },
    usage: { inputTokens: 500, outputTokens: 300 },
  };
}

// ═══════════════════════════════════════════════════════════════════════════
// ReviewCardGenerationExecutor
// ═══════════════════════════════════════════════════════════════════════════

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

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

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

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

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

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

      expect(result.parsed.cards).toHaveLength(2);
      expect(result.parsed.totalCount).toBe(2);
      expect(gateway.generate).toHaveBeenCalledTimes(1);
    });

    it('使用 cheap tier 和 review-card-generation prompt（与旧链路一致）', async () => {
      gateway.generate.mockResolvedValue(validGatewayResponse());

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

      const call = gateway.generate.mock.calls[0][0];
      expect(call.tier).toBe('cheap');
      expect(call.promptKey).toBe('review-card-generation');
      expect(call.promptVersion).toBe('1.0.0');
      expect(call.feature).toBe('review-card-generation');
    });

    it('构造 user message 与旧 ReviewCardGenerationWorkflow 一致', async () => {
      gateway.generate.mockResolvedValue(validGatewayResponse());

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

      const call = gateway.generate.mock.calls[0][0];
      const userMsg = call.messages[0].content;

      // 包含标题
      expect(userMsg).toContain('【知识点标题】');
      expect(userMsg).toContain(snapshot.snapshot.title);
      // 包含内容
      expect(userMsg).toContain('【知识点内容】');
      expect(userMsg).toContain(snapshot.snapshot.summary);
      // 包含 cardCount 指示
      expect(userMsg).toContain(`生成 ${snapshot.snapshot.cardCount} 张复习卡片`);
    });

    it('传递正确的 outputSchema', async () => {
      gateway.generate.mockResolvedValue(validGatewayResponse());

      await executor.execute(validSnapshot(), 120000);

      const call = gateway.generate.mock.calls[0][0];
      expect(call.outputSchema).toBeDefined();
    });

    it('传递 timeoutMs 到 AiGateway', async () => {
      gateway.generate.mockResolvedValue(validGatewayResponse());

      await executor.execute(validSnapshot(), 45000);

      const callArgs = gateway.generate.mock.calls[0];
      expect(callArgs[1]).toBe(45000);
    });

    it('不写数据库（无 Prisma 依赖）', async () => {
      // Verify no Prisma injection in constructor
      const deps = Reflect.getMetadata('self:paramtypes', ReviewCardGenerationExecutor) || [];
      const hasPrisma = deps.some((d: any) =>
        d?.name?.toLowerCase().includes('prisma'),
      );
      // Executor only injects AiGatewayService
      expect(hasPrisma).toBe(false);
    });

    it('Provider 失败 → 错误传播', async () => {
      gateway.generate.mockRejectedValue(new Error('Provider timeout'));

      await expect(
        executor.execute(validSnapshot(), 120000),
      ).rejects.toThrow('Provider timeout');
    });

    it('AiGateway 调用参数兼容旧链路（feature/promptKey/tier）', async () => {
      gateway.generate.mockResolvedValue(validGatewayResponse());

      await executor.execute(validSnapshot(), 120000);

      const call = gateway.generate.mock.calls[0][0];
      // Old workflow: feature='review-card-generation', tier='cheap', promptKey='review-card-generation'
      expect(call.feature).toBe('review-card-generation');
      expect(call.tier).toBe('cheap');
      expect(call.promptKey).toBe('review-card-generation');
      expect(call.promptVersion).toBe('1.0.0');
    });
  });
});

// ═══════════════════════════════════════════════════════════════════════════
// ReviewCardGenerationValidator
// ═══════════════════════════════════════════════════════════════════════════

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

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

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

  describe('正常输出', () => {
    it('合法输出验证通过', () => {
      expect(() =>
        validator.validate({
          cards: [
            {
              frontText: '什么是光合作用？',
              backText: '光合作用是植物利用光能...',
              difficulty: 'easy',
              tags: ['生物'],
            },
          ],
          totalCount: 1,
        }),
      ).not.toThrow();
    });

    it('多张卡片验证通过', () => {
      expect(() =>
        validator.validate({
          cards: [
            { frontText: 'Q1', backText: 'A1', difficulty: 'easy', tags: [] },
            { frontText: 'Q2', backText: 'A2', difficulty: 'medium', tags: [] },
            { frontText: 'Q3', backText: 'A3', difficulty: 'hard', tags: [] },
          ],
          totalCount: 3,
        }),
      ).not.toThrow();
    });
  });

  describe('非法 JSON / 结构错误', () => {
    it('空 cards 数组 → 拒绝', () => {
      expect(() =>
        validator.validate({ cards: [], totalCount: 0 }),
      ).toThrow(BusinessValidationError);
    });

    it('cards 不是数组 → 拒绝', () => {
      expect(() =>
        validator.validate({ cards: null as any, totalCount: 0 }),
      ).toThrow(BusinessValidationError);
    });

    it('totalCount 不匹配 cards.length → 拒绝', () => {
      expect(() =>
        validator.validate({
          cards: [
            { frontText: 'Q1', backText: 'A1', difficulty: 'easy', tags: [] },
          ],
          totalCount: 5, // says 5 but only 1 card
        }),
      ).toThrow(BusinessValidationError);
    });

    it('totalCount 不是整数 → 拒绝', () => {
      expect(() =>
        validator.validate({
          cards: [{ frontText: 'Q', backText: 'A', difficulty: 'easy', tags: [] }],
          totalCount: 1.5,
        }),
      ).toThrow(BusinessValidationError);
    });
  });

  describe('卡片数量越界', () => {
    it('超过 20 张 → 拒绝', () => {
      const cards = Array.from({ length: 21 }, (_, i) => ({
        frontText: `Q${i}`,
        backText: `A${i}`,
        difficulty: 'easy' as const,
        tags: [],
      }));
      expect(() =>
        validator.validate({ cards, totalCount: 21 }),
      ).toThrow(BusinessValidationError);
    });

    it('20 张 → 通过', () => {
      const cards = Array.from({ length: 20 }, (_, i) => ({
        frontText: `Q${i}`,
        backText: `A${i}`,
        difficulty: 'easy' as const,
        tags: [],
      }));
      expect(() =>
        validator.validate({ cards, totalCount: 20 }),
      ).not.toThrow();
    });
  });

  describe('空 front/back', () => {
    it('frontText 为空 → 拒绝', () => {
      expect(() =>
        validator.validate({
          cards: [{ frontText: '', backText: 'answer', difficulty: 'easy', tags: [] }],
          totalCount: 1,
        }),
      ).toThrow(BusinessValidationError);
    });

    it('backText 为空 → 拒绝', () => {
      expect(() =>
        validator.validate({
          cards: [{ frontText: 'question', backText: '', difficulty: 'easy', tags: [] }],
          totalCount: 1,
        }),
      ).toThrow(BusinessValidationError);
    });

    it('frontText 和 backText 都为空 → 拒绝（空卡片）', () => {
      expect(() =>
        validator.validate({
          cards: [{ frontText: '', backText: '', difficulty: 'easy', tags: [] }],
          totalCount: 1,
        }),
      ).toThrow(BusinessValidationError);
    });

    it('frontText 为 null/undefined → 拒绝', () => {
      expect(() =>
        validator.validate({
          cards: [{ frontText: null as any, backText: 'ok', difficulty: 'easy', tags: [] }],
          totalCount: 1,
        }),
      ).toThrow(BusinessValidationError);
    });
  });

  describe('重复卡片', () => {
    it('完全重复卡片（相同 frontText + backText）→ 拒绝', () => {
      expect(() =>
        validator.validate({
          cards: [
            { frontText: 'Same Q', backText: 'Same A', difficulty: 'easy', tags: [] },
            { frontText: 'Same Q', backText: 'Same A', difficulty: 'medium', tags: [] },
          ],
          totalCount: 2,
        }),
      ).toThrow(BusinessValidationError);
    });

    it('不同卡片 → 通过', () => {
      expect(() =>
        validator.validate({
          cards: [
            { frontText: 'Q1', backText: 'A1', difficulty: 'easy', tags: [] },
            { frontText: 'Q2', backText: 'A2', difficulty: 'medium', tags: [] },
          ],
          totalCount: 2,
        }),
      ).not.toThrow();
    });
  });

  describe('非法 difficulty', () => {
    it('difficulty 不在枚举中 → 拒绝', () => {
      expect(() =>
        validator.validate({
          cards: [{ frontText: 'Q', backText: 'A', difficulty: 'super_hard' as any, tags: [] }],
          totalCount: 1,
        }),
      ).toThrow(BusinessValidationError);
    });

    it('difficulty 为 undefined → 通过（Zod default "medium"）', () => {
      expect(() =>
        validator.validate({
          cards: [{ frontText: 'Q', backText: 'A', difficulty: undefined as any, tags: [] }],
          totalCount: 1,
        }),
      ).not.toThrow();
    });
  });

  describe('超长文本', () => {
    it('frontText 超过 500 → 拒绝', () => {
      const longText = 'A'.repeat(501);
      expect(() =>
        validator.validate({
          cards: [{ frontText: longText, backText: 'ok', difficulty: 'easy', tags: [] }],
          totalCount: 1,
        }),
      ).toThrow(BusinessValidationError);
    });

    it('backText 超过 1000 → 拒绝', () => {
      const longText = 'B'.repeat(1001);
      expect(() =>
        validator.validate({
          cards: [{ frontText: 'ok', backText: longText, difficulty: 'easy', tags: [] }],
          totalCount: 1,
        }),
      ).toThrow(BusinessValidationError);
    });

    it('frontText 刚好 500 → 通过', () => {
      const text = 'A'.repeat(500);
      expect(() =>
        validator.validate({
          cards: [{ frontText: text, backText: 'ok', difficulty: 'easy', tags: [] }],
          totalCount: 1,
        }),
      ).not.toThrow();
    });

    it('backText 刚好 1000 → 通过', () => {
      const text = 'B'.repeat(1000);
      expect(() =>
        validator.validate({
          cards: [{ frontText: 'ok', backText: text, difficulty: 'easy', tags: [] }],
          totalCount: 1,
        }),
      ).not.toThrow();
    });

    it('超大 frontText > 2000 → 拒绝', () => {
      const hugeText = 'H'.repeat(2001);
      expect(() =>
        validator.validate({
          cards: [{ frontText: hugeText, backText: 'ok', difficulty: 'easy', tags: [] }],
          totalCount: 1,
        }),
      ).toThrow(BusinessValidationError);
    });

    it('超大 backText > 5000 → 拒绝', () => {
      const hugeText = 'H'.repeat(5001);
      expect(() =>
        validator.validate({
          cards: [{ frontText: 'ok', backText: hugeText, difficulty: 'easy', tags: [] }],
          totalCount: 1,
        }),
      ).toThrow(BusinessValidationError);
    });
  });

  describe('模型指令 / Markdown 代码块', () => {
    it('frontText 包含 ```json 代码块 → 拒绝', () => {
      expect(() =>
        validator.validate({
          cards: [{ frontText: '```json\n{"key":"val"}\n```', backText: 'ok', difficulty: 'easy', tags: [] }],
          totalCount: 1,
        }),
      ).toThrow(BusinessValidationError);
    });

    it('backText 包含模型指令 "Here is the answer" → 拒绝', () => {
      expect(() =>
        validator.validate({
          cards: [{ frontText: 'Q', backText: 'Here is the answer: photosynthesis...', difficulty: 'easy', tags: [] }],
          totalCount: 1,
        }),
      ).toThrow(BusinessValidationError);
    });

    it('frontText 包含 "请分析" → 拒绝', () => {
      expect(() =>
        validator.validate({
          cards: [{ frontText: '请分析光合作用的过程', backText: 'answer', difficulty: 'easy', tags: [] }],
          totalCount: 1,
        }),
      ).toThrow(BusinessValidationError);
    });

    it('backText 包含 "根据上文" → 拒绝', () => {
      expect(() =>
        validator.validate({
          cards: [{ frontText: 'Q', backText: '根据上文，光合作用...', difficulty: 'easy', tags: [] }],
          totalCount: 1,
        }),
      ).toThrow(BusinessValidationError);
    });
  });

  describe('tags 验证', () => {
    it('tags 超过 5 个 → 拒绝', () => {
      expect(() =>
        validator.validate({
          cards: [{ frontText: 'Q', backText: 'A', difficulty: 'easy', tags: ['a', 'b', 'c', 'd', 'e', 'f'] }],
          totalCount: 1,
        }),
      ).toThrow(BusinessValidationError);
    });

    it('tag 长度超过 50 → 拒绝', () => {
      const longTag = 'X'.repeat(51);
      expect(() =>
        validator.validate({
          cards: [{ frontText: 'Q', backText: 'A', difficulty: 'easy', tags: [longTag] }],
          totalCount: 1,
        }),
      ).toThrow(BusinessValidationError);
    });

    it('tags 为空数组 → 通过', () => {
      expect(() =>
        validator.validate({
          cards: [{ frontText: 'Q', backText: 'A', difficulty: 'easy', tags: [] }],
          totalCount: 1,
        }),
      ).not.toThrow();
    });
  });

  describe('violations 消息', () => {
    it('包含具体违规信息', () => {
      try {
        validator.validate({ cards: [], totalCount: 0 });
        fail('Expected BusinessValidationError');
      } catch (err: any) {
        expect(err).toBeInstanceOf(BusinessValidationError);
        expect(err.code).toBe('business_validation_failed');
        expect(err.violations).toBeInstanceOf(Array);
        expect(err.violations.length).toBeGreaterThan(0);
        expect(err.violations.some((v: string) => v.includes('empty'))).toBe(true);
      }
    });

    it('多条违规一次性报告', () => {
      try {
        validator.validate({
          cards: [
            { frontText: '', backText: '', difficulty: 'invalid' as any, tags: [] },
            { frontText: '', backText: '', difficulty: 'invalid' as any, tags: [] },
          ],
          totalCount: 10,
        });
        fail('Expected BusinessValidationError');
      } catch (err: any) {
        expect(err.violations.length).toBeGreaterThan(1);
      }
    });
  });
});
