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 type { LessonPlanDocument, ExerciseBlockData, LessonPlan, LessonPlanStatus } from "./types";

interface PublishInput {
  planId: string;
  blockId: string;
  userId: string;
  classIds: string[];
  availableAt?: Date;
  dueAt?: Date;
}

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

// 类型守卫：安全地将 string 收窄为 LessonPlanStatus
const LESSON_PLAN_STATUSES = ["draft", "published", "archived"] as const;
function isLessonPlanStatus(v: string): v is LessonPlanStatus {
  return (LESSON_PLAN_STATUSES as readonly string[]).includes(v);
}

/**
 * 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";

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];
  // 类型守卫：从 Drizzle 推导类型收窄为 LessonPlan 所需字段
  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: isLessonPlanStatus(row.status) ? row.status : "draft",
    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
  const block = plan.content.nodes.find((b) => b.id === input.blockId);
  if (!block || block.type !== "exercise")
    throw new PublishServiceError("NO_EXERCISE_BLOCK");
  const data = block.data as ExerciseBlockData;
  if (data.items.length === 0) throw new PublishServiceError("NO_QUESTIONS");
  if (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");
  const newData = newBlock.data as ExerciseBlockData;

  for (let i = 0; i < newData.items.length; i++) {
    const item = newData.items[i];
    if (item.source === "inline" && item.inlineContent) {
      // 类型守卫：确保 inline 题目类型合法
      const validTypes = ["single_choice", "multiple_choice", "text", "judgment", "composite"] as const;
      const qt = item.inlineContent.type;
      if (!validTypes.includes(qt as typeof validTypes[number])) {
        throw new Error(`无效的题目类型: ${qt}`);
      }
      const questionType = qt as typeof validTypes[number];
      const questionId = await createQuestionWithRelations(
        {
          content: item.inlineContent.content,
          type: questionType,
          difficulty: item.inlineContent.difficulty,
          knowledgePointIds: item.inlineContent.knowledgePointIds,
        },
        input.userId,
      );
      newData.items[i] = {
        ...item,
        questionId,
        inlineContent: undefined,
      };
    }
  }

  // 4. 打包 exam 草稿
  const examId = createId();
  if (!plan.subjectId || !plan.gradeId) {
    throw new PublishServiceError("NO_SUBJECT_OR_GRADE");
  }
  await persistExamDraft({
    examId,
    title: `${plan.title} - 作业`,
    creatorId: input.userId,
    subjectId: plan.subjectId,
    gradeId: plan.gradeId,
    scheduledAt: undefined,
    description: `来自课案：${plan.title}`,
  });
  // 插入 examQuestions（通过 exams data-access 跨模块接口）
  await addExamQuestions(
    examId,
    newData.items.map((it, i) => ({
      questionId: it.questionId,
      score: it.score,
      order: i,
    })),
  );

  // 5. 下发作业
  const assignmentId = createId();
  const targetStudentIds = await getStudentIdsByClassIds(input.classIds);
  if (targetStudentIds.length === 0) {
    throw new PublishServiceError("NO_STUDENTS");
  }
  await createHomeworkAssignment({
    assignmentId,
    sourceExamId: examId,
    title: `${plan.title} - 作业`,
    description: `来自课案：${plan.title}`,
    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
    .update(lessonPlans)
    .set({ content: newContent })
    .where(eq(lessonPlans.id, input.planId));

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