import { Injectable, Logger, NotFoundException, ForbiddenException, BadRequestException } from '@nestjs/common';
import * as crypto from 'crypto';
import { PrismaService } from '../../infrastructure/database/prisma.service';
import { JobDefinitionRegistry } from './job-definition-registry';

/**
 * M-AI-04-02: Active Recall Snapshot Builder
 *
 * 为统一 Job Engine 构建 ActiveRecall 输入快照。
 *
 * 契约依据：docs/architecture/m-ai-04-active-recall-migration-contract.md §3
 *
 * 职责：
 * 1. 加载 ActiveRecallAnswer + ActiveRecallQuestion
 * 2. 验证记录属于当前用户（answer.userId === userId）
 * 3. 从 JobDefinitionRegistry 读取 prompt/model 配置（单一事实来源）
 * 4. 构建版本化、最小化、脱敏的快照
 * 5. 计算 contentHash（SHA256 前 16 字符）
 *
 * 禁止：
 * - 存储 JWT / API Key / Cookie / DB 连接 / PII
 * - 存储无关用户画像
 * - 硬编码 prompt/model 配置（应从 Definition 读取）
 *
 * Snapshot Schema（active-recall-v1）：
 *   userId, activeRecallId, knowledgeItemId, questionText, userAnswer,
 *   answerId, submittedAt, promptKey, promptVersion, modelTier, modelProvider,
 *   modelName, maxTokens, temperature
 *
 * ── M-AI-04-05 集成说明 ──
 * buildSnapshot(userId, targetType, targetId) 与 SnapshotBuilderService
 * 签名完全兼容（3 参数）。M-AI-04-05 只需在 AiJobCreationService 中按
 * jobType 分派：active_recall → ActiveRecallSnapshotBuilder，其他 →
 * SnapshotBuilderService。targetType='active_recall_answer', targetId=answerId。
 */

const SNAPSHOT_SCHEMA_VERSION = 'active-recall-v1';

/**
 * AiGateway 默认温度参数。
 *
 * 此值不在 JobDefinition 接口中（接口已冻结，M-AI-03 ADR-003 §2.1），
 * 而是 AiGatewayService.generate() 的硬编码常量（ai-gateway.service.ts:62）。
 * Snapshot 必须记录 Executor 实际将使用的 temperature 以保证重放一致性。
 */
const AI_GATEWAY_DEFAULT_TEMPERATURE = 0.3;

export interface ActiveRecallSnapshot {
  schemaVersion: string;
  snapshot: {
    userId: string;
    activeRecallId: string;
    knowledgeItemId: string | null;
    questionText: string;
    userAnswer: string;
    answerId: string;
    submittedAt: string;       // ISO8601
    promptKey: string;
    promptVersion: string;
    modelTier: string;
    modelProvider: string;
    modelName: string;
    maxTokens: number;
    temperature: number;
  };
}

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

  constructor(
    private readonly prisma: PrismaService,
    private readonly registry: JobDefinitionRegistry,
  ) {}

  /**
   * 构建 ActiveRecall 输入快照。
   *
   * prompt/model 配置从 JobDefinitionRegistry 读取（单一事实来源），
   * 避免与 active-recall-job-definition.ts 重复硬编码。
   *
   * @param userId - 请求用户 ID（用于所有权校验）
   * @param answerId - ActiveRecallAnswer.id
   * @returns 版本化、脱敏的快照对象
   *
   * @throws NotFoundException    答案或关联问题不存在
   * @throws ForbiddenException   答案不属于当前用户
   */
  async build(userId: string, answerId: string): Promise<ActiveRecallSnapshot> {
    // 1. 从 Registry 读取配置（单一事实来源，避免与 Definition 重复硬编码）
    const def = this.registry.get('active_recall');

    // 2. 加载答案
    const answer = await this.prisma.activeRecallAnswer.findUnique({
      where: { id: answerId },
    });
    if (!answer) {
      throw new NotFoundException(`ActiveRecallAnswer ${answerId} not found`);
    }

    // 3. 所有权校验（M-AI-04-01 关键发现 #2 P0 修复）
    if (answer.userId !== userId) {
      throw new ForbiddenException(
        `ActiveRecallAnswer ${answerId} does not belong to user ${userId}`,
      );
    }

    // 4. 加载关联问题
    if (!answer.questionId) {
      throw new NotFoundException(
        `ActiveRecallAnswer ${answerId} has no associated question`,
      );
    }

    const question = await this.prisma.activeRecallQuestion.findUnique({
      where: { id: answer.questionId },
    });
    if (!question) {
      throw new NotFoundException(
        `ActiveRecallQuestion ${answer.questionId} not found`,
      );
    }

    // 5. 构建快照（仅包含模型调用所需最小字段）
    //    prompt/model 值全部来自 Definition，temperature 来自 AiGateway 常量
    const snapshot: ActiveRecallSnapshot = {
      schemaVersion: SNAPSHOT_SCHEMA_VERSION,
      snapshot: {
        userId,
        activeRecallId: question.id,
        knowledgeItemId: question.knowledgeItemId ?? null,
        questionText: question.questionText,
        userAnswer: answer.answerText ?? '',
        answerId: answer.id,
        submittedAt: answer.submittedAt.toISOString(),
        promptKey: def.prompt.promptKey,
        promptVersion: def.prompt.promptVersion,
        modelTier: def.model.modelTier,
        modelProvider: def.model.modelProvider,
        modelName: def.model.modelName,
        maxTokens: def.model.maxTokens ?? 4096,
        temperature: AI_GATEWAY_DEFAULT_TEMPERATURE,
      },
    };

    this.logger.log(
      `Built ActiveRecall snapshot for answer=${answerId} userId=${userId} ` +
      `questionId=${question.id} promptKey=${def.prompt.promptKey}`,
    );

    return snapshot;
  }

  /**
   * M-AI-04-05 集成入口：与 SnapshotBuilderService.buildSnapshot() 签名兼容。
   *
   * targetType 必须为 'active_recall_answer'，targetId 为 ActiveRecallAnswer.id。
   *
   * @throws BadRequestException  targetType 不是 'active_recall_answer'
   * @throws NotFoundException    答案或关联问题不存在
   * @throws ForbiddenException   答案不属于当前用户
   */
  async buildSnapshot(
    userId: string,
    targetType: string,
    targetId: string,
  ): Promise<ActiveRecallSnapshot> {
    if (targetType !== 'active_recall_answer') {
      throw new BadRequestException(
        `ActiveRecallSnapshotBuilder only supports targetType='active_recall_answer', got '${targetType}'`,
      );
    }
    return this.build(userId, targetId);
  }

  /**
   * 计算快照的 contentHash（SHA256 前 16 字符）。
   *
   * 相同输入 → 相同输出；用于幂等比较和审计追溯。
   */
  computeHash(snapshot: ActiveRecallSnapshot): string {
    const serialized = JSON.stringify(snapshot);
    return crypto
      .createHash('sha256')
      .update(serialized)
      .digest('hex')
      .substring(0, 16);
  }
}
