import {
  JobDefinitionRegistry,
  DuplicateJobTypeError,
  UnknownJobTypeError,
  InvalidJobTypeError,
  InvalidQueueNameError,
  MissingSchemaError,
  InvalidTimeoutError,
  InvalidRetriesError,
  InvalidCredentialModeError,
  InvalidPromptKeyError,
  MissingBackoffError,
} from './job-definition-registry';
import { JobDefinition } from './job-definition.types';

/** 最小合法 Definition 工厂 */
function validDef(overrides?: Partial<JobDefinition>): JobDefinition {
  return {
    jobType: 'test_job',
    metadata: {
      label: 'Test Job',
      description: 'A test job definition',
      domain: 'analysis',
      version: '1.0.0',
    },
    queue: {
      queueName: 'ai-interactive',
      defaultPriority: 0,
    },
    execution: {
      timeoutMs: 30000,
      maxRetries: 2,
      retryBackoff: { type: 'exponential', delay: 2000 },
      cancellable: true,
      abortStrategy: 'fail',
    },
    input: { schemaVersion: '1.0.0' },
    output: { schemaVersion: '1.0.0' },
    prompt: {
      promptKey: 'test_prompt',
      promptVersion: '1.0.0',
    },
    model: {
      modelTier: 'primary',
      modelProvider: 'deepseek',
      modelName: 'deepseek-chat',
    },
    credential: {
      allowedModes: ['platform_key'],
      defaultMode: 'platform_key',
    },
    projectorKey: 'test_projector',
    security: {
      contentSafetyCheck: false,
      outputRedaction: false,
    },
    ...overrides,
  };
}

describe('JobDefinitionRegistry', () => {
  let registry: JobDefinitionRegistry;

  beforeEach(() => {
    registry = new JobDefinitionRegistry();
  });

  describe('注册成功', () => {
    it('合法 Definition 注册后可通过 get 获取', () => {
      const def = validDef();
      registry.register(def);
      expect(registry.get('test_job')).toBeDefined();
      expect(registry.get('test_job').metadata.label).toBe('Test Job');
    });

    it('多个不同 jobType 可共存', () => {
      registry.register(validDef({ jobType: 'job_a' }));
      registry.register(validDef({ jobType: 'job_b' }));
      expect(registry.getAll()).toHaveLength(2);
    });

    it('getAll 返回注册顺序', () => {
      registry.register(validDef({ jobType: 'job_a' }));
      registry.register(validDef({ jobType: 'job_b' }));
      registry.register(validDef({ jobType: 'job_c' }));
      const all = registry.getAll();
      expect(all.map((d) => d.jobType)).toEqual(['job_a', 'job_b', 'job_c']);
    });
  });

  describe('重复注册', () => {
    it('重复 jobType → DuplicateJobTypeError', () => {
      registry.register(validDef({ jobType: 'dup_job' }));
      expect(() => registry.register(validDef({ jobType: 'dup_job' }))).toThrow(
        DuplicateJobTypeError,
      );
    });
  });

  describe('未知 JobType', () => {
    it('get 未注册的 jobType → UnknownJobTypeError', () => {
      expect(() => registry.get('nonexistent')).toThrow(UnknownJobTypeError);
    });

    it('has 返回 false 对于未注册的 jobType', () => {
      expect(registry.has('nonexistent')).toBe(false);
    });
  });

  describe('jobType 格式校验', () => {
    it.each([
      '',           // 空
      '1st_job',    // 数字开头
      'JobType',    // 大写开头
      'a',          // 太短（< 2）
      'a'.repeat(65), // 太长（> 64）
      'job-type',   // 含连字符
      'job type',   // 含空格
    ])('非法 jobType "%s" → InvalidJobTypeError', (badType) => {
      expect(() => registry.register(validDef({ jobType: badType }))).toThrow(
        InvalidJobTypeError,
      );
    });
  });

  describe('queueName 校验', () => {
    it('非法 queueName → InvalidQueueNameError', () => {
      expect(() =>
        registry.register(
          validDef({
            queue: {
              queueName: 'ai-analysis' as any, // 旧队列，不在允许列表
              defaultPriority: 0,
            },
          }),
        ),
      ).toThrow(InvalidQueueNameError);
    });

    it.each(['ai-interactive', 'ai-background'] as const)(
      '合法 queueName "%s"',
      (name) => {
        expect(() =>
          registry.register(
            validDef({
              queue: { queueName: name, defaultPriority: 0 },
            }),
          ),
        ).not.toThrow();
      },
    );
  });

  describe('Schema 校验', () => {
    it('空 input.schemaVersion → MissingSchemaError', () => {
      expect(() =>
        registry.register(
          validDef({ input: { schemaVersion: '' } }),
        ),
      ).toThrow(MissingSchemaError);
    });

    it('空 output.schemaVersion → MissingSchemaError', () => {
      expect(() =>
        registry.register(
          validDef({ output: { schemaVersion: '   ' } }),
        ),
      ).toThrow(MissingSchemaError);
    });

    it('空 prompt.promptKey → InvalidPromptKeyError', () => {
      expect(() =>
        registry.register(
          validDef({ prompt: { promptKey: '', promptVersion: '1.0' } }),
        ),
      ).toThrow(InvalidPromptKeyError);
    });

    it('空 prompt.promptVersion → MissingSchemaError', () => {
      expect(() =>
        registry.register(
          validDef({ prompt: { promptKey: 'k', promptVersion: '' } }),
        ),
      ).toThrow(MissingSchemaError);
    });
  });

  describe('timeout 校验', () => {
    it.each([0, 500, -1, 1_000_000])(
      'timeoutMs=%s → InvalidTimeoutError',
      (badTimeout) => {
        expect(() =>
          registry.register(
            validDef({ execution: { ...validDef().execution, timeoutMs: badTimeout } }),
          ),
        ).toThrow(InvalidTimeoutError);
      },
    );

    it.each([1000, 30000, 600000])('合法 timeoutMs=%s', (timeout) => {
      expect(() =>
        registry.register(
          validDef({ execution: { ...validDef().execution, timeoutMs: timeout } }),
        ),
      ).not.toThrow();
    });
  });

  describe('maxRetries 校验', () => {
    it.each([-1, 11, 100])(
      'maxRetries=%s → InvalidRetriesError',
      (bad) => {
        expect(() =>
          registry.register(
            validDef({ execution: { ...validDef().execution, maxRetries: bad } }),
          ),
        ).toThrow(InvalidRetriesError);
      },
    );

    it.each([0, 3, 10])('合法 maxRetries=%s', (n) => {
      expect(() =>
        registry.register(
          validDef({ execution: { ...validDef().execution, maxRetries: n } }),
        ),
      ).not.toThrow();
    });
  });

  describe('retryBackoff 校验', () => {
    it('type 非 exponential → MissingBackoffError', () => {
      expect(() =>
        registry.register(
          validDef({
            execution: {
              ...validDef().execution,
              retryBackoff: { type: 'fixed' as any, delay: 1000 },
            },
          }),
        ),
      ).toThrow(MissingBackoffError);
    });

    it('delay=0 → MissingBackoffError', () => {
      expect(() =>
        registry.register(
          validDef({
            execution: {
              ...validDef().execution,
              retryBackoff: { type: 'exponential', delay: 0 },
            },
          }),
        ),
      ).toThrow(MissingBackoffError);
    });
  });

  describe('credential 校验', () => {
    it('allowedModes 为空 → InvalidCredentialModeError', () => {
      expect(() =>
        registry.register(
          validDef({
            credential: {
              allowedModes: [],
              defaultMode: 'platform_key',
            },
          }),
        ),
      ).toThrow(InvalidCredentialModeError);
    });

    it('allowedModes 含非法值 → InvalidCredentialModeError', () => {
      expect(() =>
        registry.register(
          validDef({
            credential: {
              allowedModes: ['magic_key' as any],
              defaultMode: 'platform_key',
            },
          }),
        ),
      ).toThrow(InvalidCredentialModeError);
    });

    it('defaultMode 不在 allowedModes 中 → InvalidCredentialModeError', () => {
      // defaultMode 'user_deepseek_key' 不在 allowedModes ['platform_key'] 中
      // 校验允许 defaultMode 不在 allowedModes 中（用户可选择）
      // ADR-003: allowedModes 声明"支持的"模式，defaultMode 可以是其中之一即可
      // 这里只校验 defaultMode 本身是否合法
      expect(() =>
        registry.register(
          validDef({
            credential: {
              allowedModes: ['platform_key'],
              defaultMode: 'bad_mode' as any,
            },
          }),
        ),
      ).toThrow(InvalidCredentialModeError);
    });
  });

  describe('metadata 校验', () => {
    it('空 label → MissingSchemaError', () => {
      expect(() =>
        registry.register(
          validDef({ metadata: { ...validDef().metadata, label: '' } }),
        ),
      ).toThrow(MissingSchemaError);
    });

    it('非法 domain → MissingSchemaError', () => {
      expect(() =>
        registry.register(
          validDef({
            metadata: { ...validDef().metadata, domain: 'invalid' as any },
          }),
        ),
      ).toThrow(MissingSchemaError);
    });
  });

  describe('禁止运行时覆盖', () => {
    it('已注册的 Definition 返回冻结对象', () => {
      registry.register(validDef({ jobType: 'frozen_job' }));
      const def = registry.get('frozen_job');
      expect(Object.isFrozen(def)).toBe(true);
    });
  });

  describe('onModuleInit 输出清单', () => {
    it('空 Registry 输出 warn', () => {
      const warnSpy = jest.spyOn(
        (registry as any).logger,
        'warn',
      );
      registry.onModuleInit();
      expect(warnSpy).toHaveBeenCalledWith(
        expect.stringContaining('No JobDefinitions registered'),
      );
      warnSpy.mockRestore();
    });

    it('有 Definition 时输出清单', () => {
      const logSpy = jest.spyOn(
        (registry as any).logger,
        'log',
      );
      registry.register(validDef({ jobType: 'job_a' }));
      registry.register(validDef({ jobType: 'job_b' }));
      registry.onModuleInit();
      expect(logSpy).toHaveBeenCalledWith(
        expect.stringContaining('Registry initialized with 2 definition'),
      );
      logSpy.mockRestore();
    });
  });
});
