import { Injectable, Logger, OnModuleInit } from '@nestjs/common';
import {
  JobDefinition,
  ALLOWED_QUEUE_NAMES,
  ALLOWED_CREDENTIAL_MODES,
} from './job-definition.types';

// ── Registry 专用错误 ──

export class DuplicateJobTypeError extends Error {
  constructor(public readonly jobType: string) {
    super(`Duplicate jobType "${jobType}" registered. Each jobType must be unique.`);
    this.name = 'DuplicateJobTypeError';
  }
}

export class UnknownJobTypeError extends Error {
  constructor(public readonly jobType: string) {
    super(`Unknown jobType "${jobType}". No JobDefinition registered for this type.`);
    this.name = 'UnknownJobTypeError';
  }
}

export class InvalidJobTypeError extends Error {
  constructor(public readonly jobType: string) {
    super(
      `Invalid jobType "${jobType}". Must match /^[a-z][a-z0-9_]{1,63}$/`,
    );
    this.name = 'InvalidJobTypeError';
  }
}

export class InvalidQueueNameError extends Error {
  constructor(public readonly queueName: string, public readonly jobType: string) {
    super(
      `Invalid queueName "${queueName}" for jobType "${jobType}". ` +
      `Must be one of: ${ALLOWED_QUEUE_NAMES.join(', ')}`,
    );
    this.name = 'InvalidQueueNameError';
  }
}

export class MissingSchemaError extends Error {
  constructor(
    public readonly jobType: string,
    public readonly field: string,
  ) {
    super(
      `Missing schema for jobType "${jobType}": ${field} must be non-empty`,
    );
    this.name = 'MissingSchemaError';
  }
}

export class InvalidTimeoutError extends Error {
  constructor(public readonly jobType: string, public readonly timeoutMs: number) {
    super(
      `Invalid timeoutMs ${timeoutMs} for jobType "${jobType}". Must be in [1000, 600000].`,
    );
    this.name = 'InvalidTimeoutError';
  }
}

export class InvalidRetriesError extends Error {
  constructor(public readonly jobType: string, public readonly maxRetries: number) {
    super(
      `Invalid maxRetries ${maxRetries} for jobType "${jobType}". Must be in [0, 10].`,
    );
    this.name = 'InvalidRetriesError';
  }
}

export class InvalidCredentialModeError extends Error {
  constructor(
    public readonly jobType: string,
    public readonly mode: string,
    public readonly allowed: readonly string[],
  ) {
    super(
      `Invalid credential mode "${mode}" for jobType "${jobType}". ` +
      `Allowed: ${allowed.join(', ')}`,
    );
    this.name = 'InvalidCredentialModeError';
  }
}

export class InvalidPromptKeyError extends Error {
  constructor(public readonly jobType: string, public readonly promptKey: string) {
    super(
      `Invalid promptKey "${promptKey}" for jobType "${jobType}". Must be non-empty.`,
    );
    this.name = 'InvalidPromptKeyError';
  }
}

export class MissingBackoffError extends Error {
  constructor(public readonly jobType: string) {
    super(
      `Missing retryBackoff for jobType "${jobType}". retryBackoff must have type 'exponential' and delay > 0.`,
    );
    this.name = 'MissingBackoffError';
  }
}

/**
 * M-AI-03-04: Job Definition Registry
 *
 * 启动时注册，运行时只读。所有校验在启动时完成（fail-fast），
 * 运行时 get() 仅做 unknown JobType 检查。
 *
 * 禁止：
 * - 运行时动态覆盖已注册 Definition
 * - 单文件大 switch
 * - 字符串拼接动态 import
 * - 静默覆盖重复 Job Type
 */
@Injectable()
export class JobDefinitionRegistry implements OnModuleInit {
  private readonly logger = new Logger(JobDefinitionRegistry.name);
  private readonly definitions = new Map<string, JobDefinition>();

  // ═══════════════════════════════════════════════════════════
  // 注册
  // ═══════════════════════════════════════════════════════════

  /**
   * 注册 Job Definition。必须在模块初始化（onModuleInit）时调用。
   *
   * 所有校验在注册时完成（fail-fast），运行时 get() 不再重复校验。
   *
   * @throws DuplicateJobTypeError 重复注册
   * @throws InvalidJobTypeError    jobType 格式非法
   * @throws InvalidQueueNameError  queueName 不在允许列表
   * @throws MissingSchemaError     schema 为空
   * @throws InvalidTimeoutError    timeout 超范围
   * @throws InvalidRetriesError    maxRetries 超范围
   * @throws InvalidCredentialModeError credential mode 非法
   * @throws InvalidPromptKeyError  promptKey 为空
   * @throws MissingBackoffError    backoff 非法
   */
  register(def: JobDefinition): void {
    // 0. Defense-in-depth: 生产环境拒绝 Synthetic Definition
    if (def.jobType === 'synthetic_job' && process.env.NODE_ENV === 'production') {
      throw new Error(
        'Synthetic Job Definition detected in production environment! ' +
        'This is a test-only artifact and must not be registered.',
      );
    }

    // 1. 查重
    if (this.definitions.has(def.jobType)) {
      throw new DuplicateJobTypeError(def.jobType);
    }

    // 2. 校验（全部 fail-fast）
    this.validate(def);

    // 3. 注册
    this.definitions.set(def.jobType, Object.freeze(def));
    this.logger.log(
      `Registered: jobType="${def.jobType}" queue="${def.queue.queueName}" ` +
      `timeout=${def.execution.timeoutMs}ms retries=${def.execution.maxRetries}`,
    );
  }

  // ═══════════════════════════════════════════════════════════
  // 查询
  // ═══════════════════════════════════════════════════════════

  /**
   * 根据 jobType 获取 JobDefinition。
   *
   * @throws UnknownJobTypeError 未注册的 jobType
   */
  get(jobType: string): JobDefinition {
    const def = this.definitions.get(jobType);
    if (!def) throw new UnknownJobTypeError(jobType);
    return def;
  }

  /**
   * 获取所有已注册的 Definition。返回顺序为注册顺序（Map 保持插入顺序）。
   */
  getAll(): JobDefinition[] {
    return Array.from(this.definitions.values());
  }

  /**
   * 检查 jobType 是否已注册。
   */
  has(jobType: string): boolean {
    return this.definitions.has(jobType);
  }

  // ═══════════════════════════════════════════════════════════
  // 生命周期
  // ═══════════════════════════════════════════════════════════

  onModuleInit(): void {
    const count = this.definitions.size;
    if (count === 0) {
      this.logger.warn('No JobDefinitions registered. Registry is empty.');
    } else {
      this.logger.log(`Registry initialized with ${count} definition(s):`);
      for (const def of this.definitions.values()) {
        this.logger.log(
          `  - ${def.jobType} → ${def.metadata.label} (queue: ${def.queue.queueName})`,
        );
      }
    }
  }

  // ═══════════════════════════════════════════════════════════
  // 校验
  // ═══════════════════════════════════════════════════════════

  private validate(def: JobDefinition): void {
    // jobType 格式
    if (!def.jobType.match(/^[a-z][a-z0-9_]{1,63}$/)) {
      throw new InvalidJobTypeError(def.jobType);
    }

    // queueName 必须在允许列表
    if (!(ALLOWED_QUEUE_NAMES as readonly string[]).includes(def.queue.queueName)) {
      throw new InvalidQueueNameError(def.queue.queueName, def.jobType);
    }

    // input schema 非空
    if (!def.input.schemaVersion || def.input.schemaVersion.trim().length === 0) {
      throw new MissingSchemaError(def.jobType, 'input.schemaVersion');
    }

    // output schema 非空
    if (!def.output.schemaVersion || def.output.schemaVersion.trim().length === 0) {
      throw new MissingSchemaError(def.jobType, 'output.schemaVersion');
    }

    // promptKey 非空
    if (!def.prompt.promptKey || def.prompt.promptKey.trim().length === 0) {
      throw new InvalidPromptKeyError(def.jobType, def.prompt.promptKey);
    }

    // promptVersion 非空
    if (!def.prompt.promptVersion || def.prompt.promptVersion.trim().length === 0) {
      throw new MissingSchemaError(def.jobType, 'prompt.promptVersion');
    }

    // timeoutMs [1000, 600000]
    if (
      typeof def.execution.timeoutMs !== 'number' ||
      def.execution.timeoutMs < 1000 ||
      def.execution.timeoutMs > 600_000
    ) {
      throw new InvalidTimeoutError(def.jobType, def.execution.timeoutMs);
    }

    // maxRetries [0, 10]
    if (
      typeof def.execution.maxRetries !== 'number' ||
      def.execution.maxRetries < 0 ||
      def.execution.maxRetries > 10
    ) {
      throw new InvalidRetriesError(def.jobType, def.execution.maxRetries);
    }

    // retryBackoff
    if (
      !def.execution.retryBackoff ||
      def.execution.retryBackoff.type !== 'exponential' ||
      typeof def.execution.retryBackoff.delay !== 'number' ||
      def.execution.retryBackoff.delay < 1
    ) {
      throw new MissingBackoffError(def.jobType);
    }

    // credential: allowedModes 非空且值合法（校验数组引用自 job-definition.types.ts）
    if (
      !def.credential.allowedModes ||
      def.credential.allowedModes.length === 0
    ) {
      throw new InvalidCredentialModeError(
        def.jobType,
        '<empty>',
        ALLOWED_CREDENTIAL_MODES,
      );
    }
    for (const mode of def.credential.allowedModes) {
      if (!ALLOWED_CREDENTIAL_MODES.includes(mode)) {
        throw new InvalidCredentialModeError(
          def.jobType,
          mode,
          ALLOWED_CREDENTIAL_MODES,
        );
      }
    }
    if (!ALLOWED_CREDENTIAL_MODES.includes(def.credential.defaultMode)) {
      throw new InvalidCredentialModeError(
        def.jobType,
        def.credential.defaultMode,
        ALLOWED_CREDENTIAL_MODES,
      );
    }

    // metadata 必填字段
    if (!def.metadata.label || def.metadata.label.trim().length === 0) {
      throw new MissingSchemaError(def.jobType, 'metadata.label');
    }
    if (
      !['analysis', 'generation', 'import', 'maintenance'].includes(
        def.metadata.domain,
      )
    ) {
      throw new MissingSchemaError(def.jobType, 'metadata.domain');
    }
  }
}
