import { Test, TestingModule } from '@nestjs/testing';
import { BadRequestException } from '@nestjs/common';
import { FeynmanExecutionRouter } from './feynman-execution-router';
import { FeatureFlagService } from '../config/feature-flag.service';
import { FeynmanSnapshotBuilder } from '../ai-job/feynman-snapshot-builder';
import { AiJobCreationService } from '../ai-job/ai-job-creation.service';
import { JobDefinitionRegistry } from '../ai-job/job-definition-registry';
import { AiAnalysisService } from './ai-analysis.service';
import { FEYNMAN_JOB_DEFINITION } from '../ai-job/feynman-job-definition';

// ═══════════════════════════════════════════════════════════════════════════
// FeynmanExecutionRouter
// ═══════════════════════════════════════════════════════════════════════════

describe('FeynmanExecutionRouter', () => {
  let router: FeynmanExecutionRouter;
  let featureFlag: any;
  let snapshotBuilder: any;
  let creationService: any;
  let registry: any;
  let legacyService: any;

  const validInput = {
    knowledgeItemTitle: '光合作用',
    knowledgeItemContent: '光合作用是植物利用光能的过程。',
    userExplanation: '光合作用就像植物做饭。',
    sessionId: 'session-001',
    answerId: 'answer-001',
  };

  const mockSnapshot = {
    schemaVersion: 'feynman-evaluation-v1',
    snapshot: {
      userId: 'u-001',
      knowledgeItemId: 'ki-001',
      submissionId: 'session-001:answer-001',
    },
  };

  const mockCreateResult = {
    job: { id: 'job-new-001' },
    snapshot: { id: 'snap-001' },
    outboxEvent: { id: 'outbox-001' },
    isDuplicate: false,
  };

  beforeEach(async () => {
    featureFlag = {
      isEnabled: jest.fn().mockResolvedValue(false), // default: legacy
    };

    snapshotBuilder = {
      build: jest.fn().mockResolvedValue(mockSnapshot),
      computeHash: jest.fn().mockReturnValue('abc123def4567890'),
    };

    creationService = {
      createJob: jest.fn().mockResolvedValue(mockCreateResult),
    };

    registry = {
      get: jest.fn().mockReturnValue(FEYNMAN_JOB_DEFINITION),
    };

    legacyService = {
      evaluateFeynman: jest.fn().mockResolvedValue({
        jobId: 'job-legacy-001',
        status: 'queued',
      }),
    };

    const module: TestingModule = await Test.createTestingModule({
      providers: [
        FeynmanExecutionRouter,
        { provide: FeatureFlagService, useValue: featureFlag },
        { provide: FeynmanSnapshotBuilder, useValue: snapshotBuilder },
        { provide: AiJobCreationService, useValue: creationService },
        { provide: JobDefinitionRegistry, useValue: registry },
        { provide: AiAnalysisService, useValue: legacyService },
      ],
    }).compile();

    router = module.get(FeynmanExecutionRouter);
  });

  describe('参数校验', () => {
    it('knowledgeItemTitle 为空 → BadRequestException', async () => {
      await expect(
        router.evaluateFeynman('u-001', { ...validInput, knowledgeItemTitle: '' }),
      ).rejects.toThrow(BadRequestException);
    });

    it('knowledgeItemContent 为空 → BadRequestException', async () => {
      await expect(
        router.evaluateFeynman('u-001', { ...validInput, knowledgeItemContent: '  ' }),
      ).rejects.toThrow(BadRequestException);
    });

    it('userExplanation 为空 → BadRequestException', async () => {
      await expect(
        router.evaluateFeynman('u-001', { ...validInput, userExplanation: '' }),
      ).rejects.toThrow(BadRequestException);
    });
  });

  describe('Legacy 路径', () => {
    it('Feature Flag 为 disabled → 走 Legacy', async () => {
      featureFlag.isEnabled.mockResolvedValue(false);

      const result = await router.evaluateFeynman('u-001', validInput);

      expect(legacyService.evaluateFeynman).toHaveBeenCalledWith('u-001', validInput);
      expect(result).toEqual({ jobId: 'job-legacy-001', status: 'queued' });
      // Unified 路径不应被调用
      expect(snapshotBuilder.build).not.toHaveBeenCalled();
      expect(creationService.createJob).not.toHaveBeenCalled();
    });

    it('FeatureFlag 查询失败 → 安全回退到 Legacy', async () => {
      featureFlag.isEnabled.mockRejectedValue(new Error('Redis connection error'));

      const result = await router.evaluateFeynman('u-001', validInput);

      expect(legacyService.evaluateFeynman).toHaveBeenCalled();
      expect(result).toEqual({ jobId: 'job-legacy-001', status: 'queued' });
    });
  });

  describe('Unified 路径', () => {
    it('Feature Flag 为 enabled → 走 Unified', async () => {
      featureFlag.isEnabled.mockResolvedValue(true);

      const result = await router.evaluateFeynman('u-001', validInput, 'ki-001');

      // Legacy 不应被调用
      expect(legacyService.evaluateFeynman).not.toHaveBeenCalled();

      // Unified 路径
      expect(snapshotBuilder.build).toHaveBeenCalledTimes(1);
      const snapshotCall = snapshotBuilder.build.mock.calls[0][0];
      expect(snapshotCall.userId).toBe('u-001');
      expect(snapshotCall.knowledgeItemId).toBe('ki-001');
      expect(snapshotCall.knowledgeItemTitle).toBe('光合作用');
      expect(snapshotCall.userExplanation).toBe('光合作用就像植物做饭。');
      expect(snapshotCall.submissionId).toBe('session-001:answer-001');

      // AiJobCreationService 调用
      expect(creationService.createJob).toHaveBeenCalledTimes(1);
      const createCall = creationService.createJob.mock.calls[0][0];
      expect(createCall.jobType).toBe('feynman_evaluation');
      expect(createCall.triggerType).toBe('user_api');
      expect(createCall.targetType).toBe('knowledge_item');
      expect(createCall.idempotencyKey).toBe('feynman:session-001:answer-001');
      expect(createCall.retrySnapshotContent).toBeDefined();

      // 响应兼容
      expect(result).toHaveProperty('jobId');
      expect(result).toHaveProperty('status', 'queued');
      expect(result).toHaveProperty('engineMode', 'unified');
      expect(result).toHaveProperty('lifecycleStatus', 'queued');
    });

    it('knowledgeItemId 未传入时使用 unknown 占位', async () => {
      featureFlag.isEnabled.mockResolvedValue(true);

      await router.evaluateFeynman('u-001', validInput);

      const snapshotCall = snapshotBuilder.build.mock.calls[0][0];
      expect(snapshotCall.knowledgeItemId).toBe('unknown');
    });

    it('Unified 失败不得自动调用 Legacy', async () => {
      featureFlag.isEnabled.mockResolvedValue(true);
      snapshotBuilder.build.mockRejectedValue(new Error('KnowledgeItem not found'));

      await expect(
        router.evaluateFeynman('u-001', validInput, 'ki-001'),
      ).rejects.toThrow('KnowledgeItem not found');

      // Legacy 不应被调用
      expect(legacyService.evaluateFeynman).not.toHaveBeenCalled();
    });
  });

  describe('幂等键', () => {
    it('sessionId + answerId 都存在 → feynman:<sessionId>:<answerId>', async () => {
      featureFlag.isEnabled.mockResolvedValue(true);

      await router.evaluateFeynman('u-001', {
        ...validInput,
        sessionId: 'sess-123',
        answerId: 'ans-456',
      }, 'ki-001');

      const createCall = creationService.createJob.mock.calls[0][0];
      expect(createCall.idempotencyKey).toBe('feynman:sess-123:ans-456');
    });

    it('仅 sessionId → feynman:<sessionId>', async () => {
      featureFlag.isEnabled.mockResolvedValue(true);

      await router.evaluateFeynman('u-001', {
        ...validInput,
        sessionId: 'sess-only',
        answerId: undefined,
      }, 'ki-001');

      const createCall = creationService.createJob.mock.calls[0][0];
      expect(createCall.idempotencyKey).toBe('feynman:sess-only');
    });

    it('无 sessionId/answerId → 基于内容的 hash 回退', async () => {
      featureFlag.isEnabled.mockResolvedValue(true);

      await router.evaluateFeynman('u-001', {
        ...validInput,
        sessionId: undefined,
        answerId: undefined,
      }, 'ki-001');

      const createCall = creationService.createJob.mock.calls[0][0];
      expect(createCall.idempotencyKey).toMatch(/^feynman:[0-9a-f]{16}$/);
    });

    it('相同内容产生相同 idempotencyKey', async () => {
      featureFlag.isEnabled.mockResolvedValue(true);

      await router.evaluateFeynman('u-001', {
        ...validInput,
        sessionId: undefined,
        answerId: undefined,
      }, 'ki-001');

      const key1 = creationService.createJob.mock.calls[0][0].idempotencyKey;

      // 第二次调用
      await router.evaluateFeynman('u-002', {
        ...validInput,
        sessionId: undefined,
        answerId: undefined,
      }, 'ki-002');

      const key2 = creationService.createJob.mock.calls[1][0].idempotencyKey;

      // 相同内容 → 相同 key（内容 hash）
      expect(key1).toBe(key2);
    });
  });

  describe('响应兼容', () => {
    it('Legacy 响应保持旧格式', async () => {
      featureFlag.isEnabled.mockResolvedValue(false);

      const result = await router.evaluateFeynman('u-001', validInput);

      expect(result).toEqual({ jobId: 'job-legacy-001', status: 'queued' });
      expect(result).not.toHaveProperty('engineMode');
      expect(result).not.toHaveProperty('lifecycleStatus');
    });

    it('Unified 响应包含 engineMode 和 lifecycleStatus', async () => {
      featureFlag.isEnabled.mockResolvedValue(true);

      const result = await router.evaluateFeynman('u-001', validInput, 'ki-001');

      expect(result).toHaveProperty('jobId');
      expect(result).toHaveProperty('status', 'queued');
      expect((result as any).engineMode).toBe('unified');
      expect((result as any).lifecycleStatus).toBe('queued');
    });
  });
});
