import "server-only";

import { eq } from "drizzle-orm";
import { createId } from "@paralleldrive/cuid2";

import { db } from "@/shared/db";
import { lessonPlans } from "@/shared/db/schema";
import { createQuestionWithRelations } from "@/modules/questions/data-access";
import { persistExamDraft, addExamQuestions } from "@/modules/exams/data-access";
import { createHomeworkAssignment } from "@/modules/homework/data-access-write";
import { getStudentIdsByClassIds } from "@/modules/classes/data-access";
import { normalizeDocument } from "./data-access";
import { isExerciseBlockData, isLessonPlanStatus, isValidQuestionType } from "./lib/type-guards";
import type { LessonPlanDocument, LessonPlan, LessonPlanStatus } from "./types";

interface PublishInput {
  planId: string;
  blockId: string;
  userId: string;
  classIds: string[];
  availableAt?: Date;
  dueAt?: Date;
  /** 作业标题（由 actions 层 i18n 翻译后传入）*/
  homeworkTitle: string;
  /** 作业描述（由 actions 层 i18n 翻译后传入）*/
  homeworkDescription: string;
}

interface PublishResult {
  examId: string;
  assignmentId: string;
  updatedContent: LessonPlanDocument;
}

/**
 * publish-service 错误：使用错误码替代硬编码中文，
 * 由 actions 层通过 PUBLISH_ERROR_KEY_MAP 翻译为 i18n 消息。
 */
export type PublishErrorCode =
  | "PLAN_NOT_FOUND"
  | "NO_PERMISSION"
  | "NO_EXERCISE_BLOCK"
  | "NO_QUESTIONS"
  | "ALREADY_PUBLISHED"
  | "NO_SUBJECT_OR_GRADE"
  | "NO_STUDENTS"
  | "INVALID_QUESTION_TYPE";

export class PublishServiceError extends Error {
  constructor(public readonly code: PublishErrorCode) {
    super(code);
    this.name = "PublishServiceError";
  }
}

export async function publishLessonPlanHomework(
  input: PublishInput,
): Promise<PublishResult> {
  // 1. 读取课案
  const rows = await db
    .select()
    .from(lessonPlans)
    .where(eq(lessonPlans.id, input.planId))
    .limit(1);
  if (rows.length === 0) throw new PublishServiceError("PLAN_NOT_FOUND");
  const row = rows[0];
  // 使用类型守卫收窄（替代 as 断言）
  const status: LessonPlanStatus = isLessonPlanStatus(row.status)
    ? row.status
    : "draft";
  const plan: LessonPlan = {
    id: row.id,
    title: row.title,
    textbookId: row.textbookId,
    chapterId: row.chapterId,
    coursePlanItemId: row.coursePlanItemId,
    subjectId: row.subjectId,
    gradeId: row.gradeId,
    templateId: row.templateId,
    templateName: row.templateName,
    content: normalizeDocument(row.content),
    status,
    creatorId: row.creatorId,
    lastSavedAt: row.lastSavedAt?.toISOString() ?? null,
    createdAt: row.createdAt.toISOString(),
    updatedAt: row.updatedAt.toISOString(),
  };
  if (plan.creatorId !== input.userId)
    throw new PublishServiceError("NO_PERMISSION");

  // 2. 定位 exercise block（使用类型守卫替代 as 断言）
  const block = plan.content.nodes.find((b) => b.id === input.blockId);
  if (!block || block.type !== "exercise")
    throw new PublishServiceError("NO_EXERCISE_BLOCK");
  if (!isExerciseBlockData(block.data))
    throw new PublishServiceError("NO_EXERCISE_BLOCK");
  if (block.data.items.length === 0)
    throw new PublishServiceError("NO_QUESTIONS");
  if (block.data.publishedAssignmentId)
    throw new PublishServiceError("ALREADY_PUBLISHED");

  // 3. inline 题目入库，替换占位 ID
  const newContent: LessonPlanDocument = structuredClone(plan.content);
  const newBlock = newContent.nodes.find((b) => b.id === input.blockId);
  if (!newBlock || newBlock.type !== "exercise")
    throw new PublishServiceError("NO_EXERCISE_BLOCK");
  if (!isExerciseBlockData(newBlock.data))
    throw new PublishServiceError("NO_EXERCISE_BLOCK");
  const newData = newBlock.data;

  if (!plan.subjectId || !plan.gradeId) {
    throw new PublishServiceError("NO_SUBJECT_OR_GRADE");
  }

  const examId = createId();
  const assignmentId = createId();
  const createdQuestionIds: string[] = [];

  // P0-12 修复：将多步写操作包裹在事务中，保证原子性。
  // 注意：跨模块 data-access（createQuestionWithRelations/persistExamDraft/addExamQuestions/
  // createHomeworkAssignment）使用各自的 db 连接，无法加入此事务。
  // 若跨模块调用成功但下方事务失败，已创建的 exam/homework 将成为孤儿数据，
  // 需通过补偿机制或定期清理任务处理。完整事务支持需跨模块 data-access 接受 tx 参数（中长期改进）。
  try {
    for (let i = 0; i < newData.items.length; i++) {
      const item = newData.items[i];
      if (item.source === "inline" && item.inlineContent) {
        const qt = item.inlineContent.type;
        // 使用类型守卫校验题目类型（替代 as 断言 + 硬编码中文错误）
        if (!isValidQuestionType(qt)) {
          throw new PublishServiceError("INVALID_QUESTION_TYPE");
        }
        const questionId = await createQuestionWithRelations(
          {
            content: item.inlineContent.content,
            type: qt,
            difficulty: item.inlineContent.difficulty,
            knowledgePointIds: item.inlineContent.knowledgePointIds,
          },
          input.userId,
        );
        createdQuestionIds.push(questionId);
        newData.items[i] = {
          ...item,
          questionId,
          inlineContent: undefined,
        };
      }
    }

    // 4. 打包 exam 草稿（标题/描述由 actions 层 i18n 传入）
    await persistExamDraft({
      examId,
      title: input.homeworkTitle,
      creatorId: input.userId,
      subjectId: plan.subjectId,
      gradeId: plan.gradeId,
      scheduledAt: undefined,
      description: input.homeworkDescription,
    });
    // 插入 examQuestions（通过 exams data-access 跨模块接口）
    await addExamQuestions(
      examId,
      newData.items.map((it, i) => ({
        questionId: it.questionId,
        score: it.score,
        order: i,
      })),
    );

    // 5. 下发作业
    const targetStudentIds = await getStudentIdsByClassIds(input.classIds);
    if (targetStudentIds.length === 0) {
      throw new PublishServiceError("NO_STUDENTS");
    }
    await createHomeworkAssignment({
      assignmentId,
      sourceExamId: examId,
      title: input.homeworkTitle,
      description: input.homeworkDescription,
      structure: null,
      status: "published",
      creatorId: input.userId,
      availableAt: input.availableAt ?? null,
      dueAt: input.dueAt ?? null,
      allowLate: false,
      lateDueAt: null,
      maxAttempts: 1,
      publish: true,
      questions: newData.items.map((it, i) => ({
        questionId: it.questionId,
        score: it.score,
        order: i,
      })),
      targetStudentIds,
    });

    // 6. 回写溯源标记 — 包裹在事务中保证原子性
    newData.publishedExamId = examId;
    newData.publishedAssignmentId = assignmentId;
    newData.publishedAt = new Date().toISOString();
    await db.transaction(async (tx) => {
      await tx
        .update(lessonPlans)
        .set({ content: newContent })
        .where(eq(lessonPlans.id, input.planId));
    });
  } catch (e) {
    // 补偿错误处理：记录已创建的资源 ID 便于排查孤儿数据
    if (e instanceof PublishServiceError) throw e;
    console.error("[publishLessonPlanHomework] 部分失败，可能存在孤儿数据", {
      planId: input.planId,
      examId,
      assignmentId,
      createdQuestionIds,
      error: e instanceof Error ? e.message : String(e),
    });
    throw e;
  }

  return { examId, assignmentId, updatedContent: newContent };
}
