import { Injectable, Logger } from '@nestjs/common';
import type { Prisma } from '@prisma/client';
import { isValidQuestionType } from '../quiz/quiz-type.contract';
import {
  ResultProjector,
  ProjectionContext,
  ArtifactReference,
} from './result-projector.interface';

/**
 * M-AI-07-04: Quiz Generation Result Projector
 *
 * 将验证后的 Quiz AI 输出原子投影到现有业务模型。
 *
 * 契约依据：docs/architecture/m-ai-07-quiz-generation-migration-contract.md §7, §8
 *
 * 在 Prisma Transaction 内与 AiJobArtifact + markSucceeded 共享事务：
 *   1. Quiz — 测验记录（sourceType='ai', status='ready'）
 *   2. QuizQuestion × N — 题目记录（type/stem/options/answer/explanation）
 *   3. AiJobArtifact — Quiz 引用
 *
 * 幂等策略：
 *   - 入口幂等：检查已有 Artifact → 直接返回
 *   - Artifact：upsertArtifact + P2002 catch
 *   - Engine CAS 保证无并发写入同一 jobId
 */

@Injectable()
export class QuizGenerationProjector implements ResultProjector {
  readonly key = 'quiz_generation_projector';
  private readonly logger = new Logger(QuizGenerationProjector.name);

  async project(
    tx: Prisma.TransactionClient,
    context: ProjectionContext,
  ): Promise<ArtifactReference[]> {
    const { job, validatedOutput } = context;
    let ordinal = 0;

    // ── 入口幂等：已有 Artifact → 直接返回 ──
    const existingArtifacts = await tx.aiJobArtifact.findMany({
      where: { jobId: job.id },
      orderBy: { ordinal: 'asc' },
    });
    if (existingArtifacts.length > 0) {
      this.logger.log(
        `Quiz Projector: returning ${existingArtifacts.length} existing artifact(s) for job=${job.id}`,
      );
      return existingArtifacts.map((a) => ({
        artifactType: a.artifactType,
        artifactId: a.artifactId,
        ordinal: a.ordinal,
      }));
    }

    const artifacts: ArtifactReference[] = [];
    const output = validatedOutput as any;

    // ═════════════════════════════════════════════════════════
    // 1. Quiz
    // ═════════════════════════════════════════════════════════

    const questions = Array.isArray(output.questions) ? output.questions : [];

    // M-QUIZ-01-CLEANUP-01: 写数据库前全量校验题型合法性
    // 任一非法 → 拒绝整个 Quiz（原子回滚，Job 不得 succeeded）
    const illegalTypes: Array<{ index: number; type: unknown }> = [];
    for (let i = 0; i < questions.length; i++) {
      if (!isValidQuestionType(questions[i].type)) {
        illegalTypes.push({ index: i, type: questions[i].type });
      }
    }
    if (illegalTypes.length > 0) {
      const details = illegalTypes.map(t => `questions[${t.index}].type="${t.type}"`).join(', ');
      this.logger.error(
        `Quiz Projector: rejecting entire quiz for job=${job.id} — ` +
        `${illegalTypes.length} illegal type(s): ${details}` +
        `promptVersion=${job.promptVersion} outputSchemaVersion=${(job as any).outputSchemaVersion}`,
      );
      throw new Error(
        `QUIZ_INVALID_QUESTION_TYPE: ${illegalTypes.length} question(s) have invalid type. ` +
        `Allowed: choice, fill, judge. Got: ${details}`,
      );
    }

    const validQuestions = questions.filter(
      (q: any) => q.stem && q.answer,
    );

    if (validQuestions.length === 0) {
      this.logger.warn(`Quiz Projector: no valid questions for job=${job.id}`);
      return artifacts; // empty — no Quiz created
    }

    const quiz = await tx.quiz.create({
      data: {
        userId: job.userId,
        knowledgeBaseId: job.targetId ?? 'unknown',
        title: output.quizTitle ?? 'AI Generated Quiz',
        description: output.quizDescription ?? null,
        questionCount: validQuestions.length,
        sourceType: 'ai',
        sourceId: job.id,
        status: 'ready',
      },
    });

    // Quiz Artifact
    await upsertArtifact(tx, job.id, 'Quiz', quiz.id, ordinal);
    artifacts.push({
      artifactType: 'Quiz',
      artifactId: quiz.id,
      ordinal: ordinal++,
    });

    this.logger.log(
      `Quiz Projector: Quiz ${quiz.id} created for job=${job.id} with ${validQuestions.length} questions`,
    );

    // ═════════════════════════════════════════════════════════
    // 2. QuizQuestion × N
    // ═════════════════════════════════════════════════════════

    for (let i = 0; i < validQuestions.length; i++) {
      const q = validQuestions[i];

      await tx.quizQuestion.create({
        data: {
          quizId: quiz.id,
          // M-QUIZ-01-CLEANUP-01: type 已在全量预检中验证，此处必为合法值
          type: q.type,
          stem: q.stem,
          options: Array.isArray(q.options) ? q.options : undefined,
          answer: q.answer,
          explanation: q.explanation ?? null,
          sourceBlockIds: Array.isArray(q.sourceBlockIds) ? q.sourceBlockIds : undefined,
          orderIndex: i,
        },
      });
    }

    this.logger.log(
      `Quiz Projector: ${validQuestions.length} Question(s) written for quiz=${quiz.id}`,
    );

    // ═════════════════════════════════════════════════════════
    // 完成
    // ═════════════════════════════════════════════════════════

    this.logger.log(
      `Quiz Projector: ${artifacts.length} artifact(s) total for job=${job.id}`,
    );
    return artifacts;
  }
}

// ── Helpers ──

async function upsertArtifact(
  tx: Prisma.TransactionClient,
  jobId: string,
  artifactType: string,
  artifactId: string,
  ordinal: number,
): Promise<void> {
  try {
    await tx.aiJobArtifact.create({
      data: { jobId, artifactType, artifactId, ordinal },
    });
  } catch (err: any) {
    if (err?.code === 'P2002') return;
    throw err;
  }
}
