import { Injectable, Logger, BadRequestException } from '@nestjs/common';
import * as crypto from 'crypto';
import { PrismaService } from '../../infrastructure/database/prisma.service';
import { OutboxRepository } from '../../infrastructure/outbox/outbox.repository';
import { SnapshotBuilderService } from '../ai-runtime/snapshot-builder.service';
import { ActiveRecallSnapshotBuilder } from './active-recall-snapshot-builder';
import { AiJobLifecycleRepository, CreateJobInput } from './ai-job-lifecycle.repository';
import { JobDefinitionRegistry } from './job-definition-registry';
import type { JobDefinition } from './job-definition.types';
import { Prisma } from '@prisma/client';

/**
 * M-AI-03-06: AiJobCreationService
 *
 * 统一 Job 创建入口（internal-only）。
 * 在一个 Prisma Transaction 内原子创建：
 *   1. AiJob（lifecycleStatus = 'queued'）
 *   2. AiJobSnapshot（不可变输入快照）
 *   3. OutboxEvent（eventType = 'ai.job.enqueue'）
 *
 * 幂等性：AiJob 表的 @@unique([userId, jobType, idempotencyKey]) 保证
 * 重复 (userId, jobType, idempotencyKey) 返回已有 Job，不重复创建。
 *
 * 约束：
 * - 不向 BullMQ 直接入队（由 OutboxDispatcher 异步投递）
 * - 不调用 Provider
 * - 不创建业务结果
 */

export interface CreateAiJobInput {
  userId: string;
  jobType: string;
  triggerType: 'user_api' | 'admin_manual' | 'system_scheduled' | 'parent_job';
  targetType: string;
  targetId: string;
  /** 父 Job ID（Job 链/Admin Retry 场景） */
  parentJobId?: string;
  /** 幂等 Key。提供时启用幂等；不提供时每次创建新 Job */
  idempotencyKey?: string;
  /** Admin Retry：预构建的 Snapshot content（复用原 Snapshot 时传入） */
  retrySnapshotContent?: Record<string, unknown>;
  /** 覆盖 Definition 默认值 */
  overrides?: {
    priority?: number;
    credentialMode?: 'platform_key' | 'user_deepseek_key';
    credentialId?: string;
  };
}

@Injectable()
export class AiJobCreationService {
  private readonly logger = new Logger(AiJobCreationService.name);

  constructor(
    private readonly prisma: PrismaService,
    private readonly lifecycleRepo: AiJobLifecycleRepository,
    private readonly registry: JobDefinitionRegistry,
    private readonly snapshotBuilder: SnapshotBuilderService,
    private readonly activeRecallSnapshotBuilder: ActiveRecallSnapshotBuilder,
    private readonly outboxRepo: OutboxRepository,
  ) {}

  /**
   * 创建 Job + Snapshot + Outbox（在自己的 Prisma Transaction 内）。
   *
   * 幂等：若 (userId, jobType, idempotencyKey) 已存在，返回已有 Job。
   *
   * 内部复用 createInTransaction 的核心逻辑。
   */
  async createJob(input: CreateAiJobInput) {
    // 1. Resolve Definition + Validate triggerType
    const def = this.resolveAndValidate(input);

    // 2. Build snapshot（事务外：依赖外部 DB 读取，不应占用事务时间）
    const snapshot = await this.buildSnapshotContent(input);

    const contentHash = this.computeHash(JSON.stringify(snapshot));

    // 3. Transaction: Job + Snapshot + Outbox（含幂等保护）
    const createFn = async (tx: Prisma.TransactionClient) => {
      return this.createCore(tx, input, def, snapshot, contentHash);
    };

    try {
      const job = await this.prisma.$transaction(createFn);
      this.logger.log(
        `Job created: id=${job.id} type=${input.jobType} ` +
        `queue=${def.queue.queueName} snapshotHash=${contentHash}`,
      );
      return job;
    } catch (err: any) {
      // 幂等回退：并发重复创建 → UNIQUE 冲突（P2002）→ 返回已有 Job
      if (err?.code === 'P2002' && input.idempotencyKey) {
        const existing = await this.prisma.aiJob.findUniqueOrThrow({
          where: {
            userId_jobType_idempotencyKey: {
              userId: input.userId,
              jobType: input.jobType,
              idempotencyKey: input.idempotencyKey,
            },
          },
        });
        this.logger.log(
          `Idempotent return (P2002): jobId=${existing.id} ` +
          `key=${input.idempotencyKey}`,
        );
        return existing;
      }
      throw err;
    }
  }

  /**
   * 在外部 Prisma 事务中创建 Job + Snapshot + Outbox。
   *
   * 使用场景：Parent Projector 需要在同一事务中原子创建 Child Job。
   * 调用方负责传入事务客户端，本方法不自行开启事务。
   *
   * 约束：
   * - 必须提供 retrySnapshotContent（Snapshot 需在事务外预构建）
   * - 幂等采用 check-before-create（避免 P2002 污染外部事务）
   * - 复用与 createJob 相同的 Definition 校验和字段构造逻辑
   *
   * @param tx - 外部 Prisma 事务客户端
   * @param input - Job 创建输入（必须含 retrySnapshotContent）
   * @returns 创建的 AiJob（或幂等返回的已有 Job）
   */
  async createInTransaction(
    tx: Prisma.TransactionClient,
    input: CreateAiJobInput,
  ) {
    // 1. Resolve Definition + Validate triggerType（与 createJob 相同）
    const def = this.resolveAndValidate(input);

    // 2. Snapshot 必须预构建（事务内不应执行 DB 读取）
    if (!input.retrySnapshotContent) {
      throw new BadRequestException(
        'createInTransaction requires retrySnapshotContent. ' +
        'Build the snapshot before opening the transaction.',
      );
    }
    const snapshot = input.retrySnapshotContent;
    const contentHash = this.computeHash(JSON.stringify(snapshot));

    // 3. 幂等：check-before-create（避免 P2002 污染外部事务）
    if (input.idempotencyKey) {
      const existing = await tx.aiJob.findUnique({
        where: {
          userId_jobType_idempotencyKey: {
            userId: input.userId,
            jobType: input.jobType,
            idempotencyKey: input.idempotencyKey,
          },
        },
      });
      if (existing) {
        this.logger.log(
          `createInTransaction idempotent return: jobId=${existing.id} ` +
          `key=${input.idempotencyKey}`,
        );
        return existing;
      }
    }

    // 4. 委托核心创建逻辑（使用传入的 tx，不复刻逻辑）
    const job = await this.createCore(tx, input, def, snapshot, contentHash);

    this.logger.log(
      `Child Job created in external tx: id=${job.id} type=${input.jobType} ` +
      `parentJobId=${input.parentJobId} snapshotHash=${contentHash}`,
    );
    return job;
  }

  // ═══════════════════════════════════════════════════════════════
  // Private helpers
  // ═══════════════════════════════════════════════════════════════

  /**
   * 核心创建逻辑（Job + Snapshot + Outbox），在给定的 tx 中执行。
   *
   * 不负责：Definition 校验、Snapshot 构建、幂等检查、事务管理。
   * 这些由 createJob 或 createInTransaction 在上层处理。
   */
  private async createCore(
    tx: Prisma.TransactionClient,
    input: CreateAiJobInput,
    def: JobDefinition,
    snapshot: Record<string, unknown>,
    contentHash: string,
  ) {
    // Create AiJob
    const jobInput: CreateJobInput = {
      userId: input.userId,
      jobType: input.jobType,
      triggerType: input.triggerType,
      queueName: def.queue.queueName,
      targetType: input.targetType,
      targetId: input.targetId,
      parentJobId: input.parentJobId,
      idempotencyKey: input.idempotencyKey,
      priority: input.overrides?.priority ?? def.queue.defaultPriority,
      credentialMode: input.overrides?.credentialMode ?? def.credential.defaultMode,
      credentialId: input.overrides?.credentialId,
      modelProvider: def.model.modelProvider,
      modelName: def.model.modelName,
      promptKey: def.prompt.promptKey,
      promptVersion: def.prompt.promptVersion,
      outputSchemaVersion: def.output.schemaVersion,
      inputSchemaVersion: def.input.schemaVersion,
      maxAttempts: def.execution.maxRetries,
      timeoutMs: def.execution.timeoutMs,
    };

    const job = await this.lifecycleRepo.createJob(tx, jobInput);

    // Create AiJobSnapshot
    await tx.aiJobSnapshot.create({
      data: {
        jobId: job.id,
        jobType: input.jobType,
        schemaVersion: def.input.schemaVersion,
        redactionVersion: '1.0',
        content: snapshot as any,
        contentHash,
      },
    });

    // Create OutboxEvent
    const dedupeKey = `ai.job.enqueue:${job.id}`;
    await this.outboxRepo.createInTransaction(tx, {
      eventType: 'ai.job.enqueue',
      aggregateType: 'AiJob',
      aggregateId: job.id,
      dedupeKey,
      payload: { jobId: job.id },
      availableAt: new Date(),
    });

    return job;
  }

  /** Resolve Definition + Validate triggerType（createJob 和 createInTransaction 共用） */
  private resolveAndValidate(input: CreateAiJobInput): JobDefinition {
    const def = this.registry.get(input.jobType);

    if (input.triggerType && !['user_api', 'admin_manual', 'system_scheduled', 'parent_job'].includes(input.triggerType)) {
      throw new BadRequestException(`Invalid triggerType: ${input.triggerType}`);
    }

    return def;
  }

  /** Build snapshot content（事务外 DB 读取），仅 createJob 使用 */
  private async buildSnapshotContent(
    input: CreateAiJobInput,
  ): Promise<Record<string, unknown>> {
    if (input.retrySnapshotContent) {
      return input.retrySnapshotContent;
    }
    if (input.jobType === 'synthetic_job') {
      return { _synthetic: true, targetType: input.targetType, targetId: input.targetId };
    }
    if (input.jobType === 'active_recall') {
      return this.activeRecallSnapshotBuilder.buildSnapshot(
        input.userId,
        input.targetType,
        input.targetId,
      ) as unknown as Record<string, unknown>;
    }
    if (input.jobType === 'feynman_evaluation') {
      throw new BadRequestException(
        'feynman_evaluation requires retrySnapshotContent. ' +
        'Use FeynmanExecutionRouter to build the snapshot before calling createJob.',
      );
    }
    return this.snapshotBuilder.buildSnapshot(
      input.userId,
      input.targetType,
      input.targetId,
    );
  }

  // ── helpers ──

  private computeHash(content: string): string {
    return crypto.createHash('sha256').update(content).digest('hex').substring(0, 16);
  }
}
