import 'server-only';

import { db } from "@/shared/db";
import { knowledgePoints, questions, questionsToKnowledgePoints } from "@/shared/db/schema";
import { and, count, desc, eq, inArray, sql, type SQL } from "drizzle-orm";
import { cache } from "react";
import { createId } from "@paralleldrive/cuid2";
import {
  getChaptersByTextbookId as getChaptersByTextbookIdFromTextbooks,
  getKnowledgePointOptions as getKnowledgePointOptionsFromTextbooks,
  getKnowledgePointsByChapterId as getKnowledgePointsByChapterIdFromTextbooks,
  getKnowledgePointsByTextbookId as getKnowledgePointsByTextbookIdFromTextbooks,
  getTextbooks as getTextbooksFromTextbooks,
} from "@/modules/textbooks/data-access";
import type { CreateQuestionInput } from "./schema";
import type { ChapterOption, KnowledgePointOption, Question, QuestionType, TextbookOption } from "./types";

type Tx = Parameters<Parameters<typeof db.transaction>[0]>[0]

export type UpdateQuestionInput = {
  type: QuestionType
  difficulty: number
  content: unknown
  knowledgePointIds?: string[]
}

export type GetQuestionsParams = {
  q?: string;
  page?: number;
  pageSize?: number;
  ids?: string[];
  knowledgePointId?: string;
  textbookId?: string;
  chapterId?: string;
  type?: QuestionType;
  difficulty?: number;
};

export const getQuestions = cache(async ({
  q,
  page = 1,
  pageSize = 50,
  ids,
  knowledgePointId,
  textbookId,
  chapterId,
  type,
  difficulty,
}: GetQuestionsParams = {}) => {
  const safePage = typeof page === "number" && page >= 1 ? page : 1
  const safePageSize = typeof pageSize === "number" && pageSize > 0 ? pageSize : 50
  const offset = (safePage - 1) * safePageSize;

  const conditions: SQL[] = [];

  if (ids && ids.length > 0) {
    conditions.push(inArray(questions.id, ids));
  }

  if (q && q.trim().length > 0) {
    const needle = `%${q.trim().toLowerCase()}%`;
    conditions.push(
      sql`LOWER(CAST(${questions.content} AS CHAR)) LIKE ${needle}`
    );
  }

  if (type) {
    conditions.push(eq(questions.type, type));
  }

  if (difficulty) {
    conditions.push(eq(questions.difficulty, difficulty));
  }

  // 级联筛选：通过 textbooks data-access 获取知识点 ID 集合，
  // 再按知识点过滤题目。避免直接查询 textbooks/chapters 表。
  if (knowledgePointId) {
    conditions.push(
      inArray(
        questions.id,
        db
          .select({ questionId: questionsToKnowledgePoints.questionId })
          .from(questionsToKnowledgePoints)
          .where(eq(questionsToKnowledgePoints.knowledgePointId, knowledgePointId))
      )
    );
  } else if (chapterId) {
    // 按章节筛选：获取该章节下所有知识点 ID
    const kps = await getKnowledgePointsByChapterIdFromTextbooks(chapterId)
    const kpIds = kps.map((kp) => kp.id)
    if (kpIds.length > 0) {
      conditions.push(
        inArray(
          questions.id,
          db
            .select({ questionId: questionsToKnowledgePoints.questionId })
            .from(questionsToKnowledgePoints)
            .where(inArray(questionsToKnowledgePoints.knowledgePointId, kpIds))
        )
      );
    } else {
      // 章节下无知识点，返回空结果
      conditions.push(sql`1 = 0`)
    }
  } else if (textbookId) {
    // 按教材筛选：获取该教材下所有知识点 ID
    const kps = await getKnowledgePointsByTextbookIdFromTextbooks(textbookId)
    const kpIds = kps.map((kp) => kp.id)
    if (kpIds.length > 0) {
      conditions.push(
        inArray(
          questions.id,
          db
            .select({ questionId: questionsToKnowledgePoints.questionId })
            .from(questionsToKnowledgePoints)
            .where(inArray(questionsToKnowledgePoints.knowledgePointId, kpIds))
        )
      );
    } else {
      conditions.push(sql`1 = 0`)
    }
  }

  if (!ids || ids.length === 0) {
    conditions.push(sql`${questions.parentId} IS NULL`)
  }

  const whereClause = conditions.length > 0 ? and(...conditions) : undefined;

  const [totalResult] = await db
    .select({ count: count() })
    .from(questions)
    .where(whereClause);
  
  const total = Number(totalResult?.count ?? 0);

  const rows = await db.query.questions.findMany({
    where: whereClause,
    limit: safePageSize,
    offset: offset,
    orderBy: [desc(questions.createdAt)],
    with: {
      knowledgePoints: {
        with: {
          knowledgePoint: true,
        },
      },
      author: {
        columns: {
          id: true,
          name: true,
          image: true,
        },
      },
      children: true,
    },
  });

  return {
    data: rows.map((row) => {
      const knowledgePoints =
        row.knowledgePoints?.map((rel) => rel.knowledgePoint) ?? [];

      const author = row.author
        ? {
            id: row.author.id,
            name: row.author.name,
            image: row.author.image,
          }
        : null;

      const mapped: Question = {
        id: row.id,
        content: row.content,
        type: row.type,
        difficulty: row.difficulty ?? 1,
        createdAt: row.createdAt,
        updatedAt: row.updatedAt,
        author,
        knowledgePoints: knowledgePoints.map((kp) => ({ id: kp.id, name: kp.name })),
        childrenCount: row.children?.length ?? 0,
      };

      return mapped;
    }),
    meta: {
      page: safePage,
      pageSize: safePageSize,
      total,
      totalPages: Math.ceil(total / safePageSize),
    },
  };
});

export type QuestionsDashboardStats = {
  questionCount: number
}

export const getQuestionsDashboardStats = cache(async (): Promise<QuestionsDashboardStats> => {
  const [row] = await db.select({ value: count() }).from(questions)
  return { questionCount: Number(row?.value ?? 0) }
})

async function insertQuestionWithRelations(
  tx: Tx,
  input: CreateQuestionInput,
  authorId: string,
  parentId: string | null = null
): Promise<string> {
  const newQuestionId = createId();

  await tx.insert(questions).values({
    id: newQuestionId,
    content: input.content,
    type: input.type,
    difficulty: input.difficulty,
    authorId: authorId,
    parentId: parentId,
  });

  if (input.knowledgePointIds && input.knowledgePointIds.length > 0) {
    await tx.insert(questionsToKnowledgePoints).values(
      input.knowledgePointIds.map((kpId) => ({
        questionId: newQuestionId,
        knowledgePointId: kpId,
      }))
    );
  }

  if (input.subQuestions && input.subQuestions.length > 0) {
    for (const subQ of input.subQuestions) {
      await insertQuestionWithRelations(tx, subQ, authorId, newQuestionId);
    }
  }

  return newQuestionId;
}

export async function createQuestionWithRelations(
  input: CreateQuestionInput,
  authorId: string
): Promise<string> {
  return await db.transaction(async (tx) => {
    return await insertQuestionWithRelations(tx, input, authorId, null);
  });
}

export async function updateQuestionById(
  id: string,
  input: UpdateQuestionInput,
  canEditAll: boolean,
  authorId: string
): Promise<void> {
  await db.transaction(async (tx) => {
    await tx
      .update(questions)
      .set({
        type: input.type,
        difficulty: input.difficulty,
        content: input.content,
        updatedAt: new Date(),
      })
      .where(
        canEditAll
          ? eq(questions.id, id)
          : and(eq(questions.id, id), eq(questions.authorId, authorId))
      );

    await tx
      .delete(questionsToKnowledgePoints)
      .where(eq(questionsToKnowledgePoints.questionId, id));

    if (input.knowledgePointIds && input.knowledgePointIds.length > 0) {
      await tx.insert(questionsToKnowledgePoints).values(
        input.knowledgePointIds.map((kpId) => ({
          questionId: id,
          knowledgePointId: kpId,
        }))
      );
    }
  });
}

async function deleteQuestionRecursive(
  tx: Tx,
  questionId: string,
  visited: Set<string> = new Set(),
): Promise<void> {
  if (visited.has(questionId)) {
    // 环检测：避免在异常数据（如循环引用）下无限递归
    return
  }
  visited.add(questionId)

  const children = await tx
    .select({ id: questions.id })
    .from(questions)
    .where(eq(questions.parentId, questionId));

  for (const child of children) {
    await deleteQuestionRecursive(tx, child.id, visited);
  }

  await tx.delete(questions).where(eq(questions.id, questionId));
}

export async function deleteQuestionByIdRecursive(
  questionId: string,
  canDeleteAll: boolean,
  authorId: string
): Promise<void> {
  await db.transaction(async (tx) => {
    const q = await tx.query.questions.findFirst({
      where: eq(questions.id, questionId),
    });

    if (!q) {
      throw new Error("Question not found");
    }

    if (!canDeleteAll && q.authorId !== authorId) {
      throw new Error("Unauthorized");
    }

    await deleteQuestionRecursive(tx, questionId);
  });
}

/**
 * 批量删除题目（级联删除子题）。
 *
 * 遵循权限范围：非 all 范围只能删除自己创建的题目。
 * 单事务保证原子性，任一题目删除失败则全部回滚。
 *
 * @param questionIds - 待删除的题目 ID 列表
 * @param canDeleteAll - 是否有全部数据范围权限
 * @param authorId - 当前用户 ID（用于权限过滤）
 * @returns 实际删除的题目数量
 */
export async function deleteQuestionsBatch(
  questionIds: string[],
  canDeleteAll: boolean,
  authorId: string
): Promise<number> {
  if (questionIds.length === 0) return 0

  const uniqueIds = Array.from(new Set(questionIds))
  return await db.transaction(async (tx) => {
    // 权限过滤：非 all 范围只能删除自己创建的题目
    const whereClause = canDeleteAll
      ? inArray(questions.id, uniqueIds)
      : and(inArray(questions.id, uniqueIds), eq(questions.authorId, authorId))

    const targetRows = await tx
      .select({ id: questions.id })
      .from(questions)
      .where(whereClause)

    const targetIds = targetRows.map((r) => r.id)
    if (targetIds.length === 0) return 0

    for (const id of targetIds) {
      await deleteQuestionRecursive(tx, id)
    }

    return targetIds.length
  })
}

export async function getKnowledgePointOptions(): Promise<KnowledgePointOption[]> {
  // Delegate to textbooks module data-access to avoid direct queries on
  // textbooks/chapters/knowledgePoints tables (owned by textbooks module).
  return await getKnowledgePointOptionsFromTextbooks()
}

/**
 * 获取教材选项列表（级联筛选第一级）。
 *
 * 委托 textbooks 模块 data-access 获取教材列表，避免直接查询 textbooks 表。
 */
export async function getTextbookOptions(): Promise<TextbookOption[]> {
  const textbooks = await getTextbooksFromTextbooks()
  return textbooks.map((tb) => ({
    id: tb.id,
    title: tb.title,
    subject: tb.subject,
    grade: tb.grade,
  }))
}

/**
 * 获取指定教材下的章节选项列表（级联筛选第二级）。
 *
 * 委托 textbooks 模块 data-access 获取章节树，展平为选项列表。
 */
export async function getChapterOptions(textbookId: string): Promise<ChapterOption[]> {
  const chapters = await getChaptersByTextbookIdFromTextbooks(textbookId)
  const options: ChapterOption[] = []

  function flatten(chs: typeof chapters, depth = 0): void {
    for (const ch of chs) {
      options.push({
        id: ch.id,
        title: ch.title,
        parentId: ch.parentId ?? null,
        depth,
      })
      if (ch.children && ch.children.length > 0) {
        flatten(ch.children, depth + 1)
      }
    }
  }

  flatten(chapters)
  return options
}

/**
 * 获取指定章节下的知识点选项列表（级联筛选第三级）。
 *
 * 委托 textbooks 模块 data-access 获取知识点列表。
 */
export async function getKnowledgePointOptionsByChapter(chapterId: string): Promise<{ id: string; name: string }[]> {
  const kps = await getKnowledgePointsByChapterIdFromTextbooks(chapterId)
  return kps.map((kp) => ({ id: kp.id, name: kp.name }))
}

// ---------------------------------------------------------------------------
// Cross-module query interfaces — read-only access for other modules
// ---------------------------------------------------------------------------

export type QuestionKnowledgePoint = {
  questionId: string
  knowledgePointId: string
  knowledgePointName: string
}

/** Returns knowledge points associated with the given question ids. */
export const getKnowledgePointsForQuestions = cache(
  async (questionIds: string[]): Promise<Map<string, QuestionKnowledgePoint[]>> => {
    const result = new Map<string, QuestionKnowledgePoint[]>()
    const uniqueIds = Array.from(new Set(questionIds.filter((v): v is string => typeof v === "string" && v.length > 0)))
    if (uniqueIds.length === 0) return result

    const rows = await db
      .select({
        questionId: questionsToKnowledgePoints.questionId,
        knowledgePointId: knowledgePoints.id,
        knowledgePointName: knowledgePoints.name,
      })
      .from(questionsToKnowledgePoints)
      .innerJoin(knowledgePoints, eq(knowledgePoints.id, questionsToKnowledgePoints.knowledgePointId))
      .where(inArray(questionsToKnowledgePoints.questionId, uniqueIds))

    for (const r of rows) {
      const list = result.get(r.questionId) ?? []
      list.push({
        questionId: r.questionId,
        knowledgePointId: r.knowledgePointId,
        knowledgePointName: r.knowledgePointName,
      })
      result.set(r.questionId, list)
    }
    return result
  }
)

/**
 * 跨模块接口：获取题目内容与类型（供 error-book 模块提取正确答案使用）。
 *
 * error-book 模块在采集错题时需要从题目内容中提取正确答案（correctAnswer），
 * 此接口返回题目的 content 和 type，由 error-book 模块调用
 * `homework/lib/question-content-utils.ts` 中的纯函数进行提取。
 */
export type QuestionContentForErrorCollection = {
  content: unknown
  type: string
}

export const getQuestionsContentForErrorCollection = cache(
  async (questionIds: string[]): Promise<Map<string, QuestionContentForErrorCollection>> => {
    const result = new Map<string, QuestionContentForErrorCollection>()
    const uniqueIds = Array.from(new Set(questionIds.filter((v): v is string => typeof v === "string" && v.length > 0)))
    if (uniqueIds.length === 0) return result

    const rows = await db
      .select({
        id: questions.id,
        content: questions.content,
        type: questions.type,
      })
      .from(questions)
      .where(inArray(questions.id, uniqueIds))

    for (const r of rows) {
      result.set(r.id, { content: r.content, type: r.type })
    }
    return result
  }
)

/**
 * 按 ID 批量获取题目类型映射。
 *
 * exams 等模块在组卷/成绩录入时需要题目 type 字段，此接口封装对 questions 表的查询，
 * 避免跨模块直接 JOIN questions 表（违反三层架构）。
 *
 * 返回 Map<questionId, type>，未找到的 ID 不会出现在 Map 中。
 */
export const getQuestionTypeMapByIds = cache(
  async (questionIds: string[]): Promise<Map<string, string>> => {
    const result = new Map<string, string>()
    const uniqueIds = Array.from(new Set(questionIds.filter((v): v is string => typeof v === "string" && v.length > 0)))
    if (uniqueIds.length === 0) return result

    const rows = await db
      .select({ id: questions.id, type: questions.type })
      .from(questions)
      .where(inArray(questions.id, uniqueIds))

    for (const r of rows) {
      result.set(r.id, r.type)
    }
    return result
  }
)

// ---------------------------------------------------------------------------
// 导入/导出接口
// ---------------------------------------------------------------------------

/** 导出题目格式（JSON） */
export interface QuestionExportItem {
  id: string
  type: QuestionType
  difficulty: number
  content: unknown
  knowledgePoints: { id: string; name: string }[]
  createdAt: Date
  updatedAt: Date
}

/**
 * 导出题目为结构化 JSON 格式。
 *
 * 支持按 IDs 导出指定题目，或导出全部题目（受 pageSize 限制）。
 * 遵循权限范围：非 all 范围只能导出自己创建的题目。
 */
export async function exportQuestions(
  questionIds?: string[],
  canExportAll = true,
  authorId?: string
): Promise<QuestionExportItem[]> {
  const conditions: SQL[] = []

  if (questionIds && questionIds.length > 0) {
    conditions.push(inArray(questions.id, questionIds))
  }

  if (!canExportAll && authorId) {
    conditions.push(eq(questions.authorId, authorId))
  }

  conditions.push(sql`${questions.parentId} IS NULL`)

  const whereClause = conditions.length > 0 ? and(...conditions) : undefined

  const rows = await db.query.questions.findMany({
    where: whereClause,
    limit: 1000,
    orderBy: [desc(questions.createdAt)],
    with: {
      knowledgePoints: {
        with: {
          knowledgePoint: true,
        },
      },
    },
  })

  return rows.map((row) => ({
    id: row.id,
    type: row.type,
    difficulty: row.difficulty ?? 1,
    content: row.content,
    knowledgePoints: (row.knowledgePoints ?? []).map((rel) => ({
      id: rel.knowledgePoint.id,
      name: rel.knowledgePoint.name,
    })),
    createdAt: row.createdAt,
    updatedAt: row.updatedAt,
  }))
}

/** 导入题目的单条输入 */
export interface QuestionImportItem {
  type: QuestionType
  difficulty: number
  content: unknown
  knowledgePointIds?: string[]
}

/**
 * 批量导入题目。
 *
 * 单事务保证原子性，任一题目导入失败则全部回滚。
 * 返回创建的题目 ID 列表。
 */
export async function importQuestions(
  items: QuestionImportItem[],
  authorId: string
): Promise<string[]> {
  if (items.length === 0) return []

  return await db.transaction(async (tx) => {
    const createdIds: string[] = []
    for (const item of items) {
      const id = await insertQuestionWithRelations(
        tx,
        {
          content: item.content,
          type: item.type,
          difficulty: item.difficulty,
          knowledgePointIds: item.knowledgePointIds,
        },
        authorId,
        null
      )
      createdIds.push(id)
    }
    return createdIds
  })
}
