import "server-only"

import { cache } from "react"
import { and, asc, count, eq, inArray, like, or, sql, isNull, type SQL } from "drizzle-orm"
import { createId } from "@paralleldrive/cuid2"

import { db } from "@/shared/db"
import { chapters, knowledgePoints, knowledgePointPrerequisites, textbooks } from "@/shared/db/schema"
import { escapeLikePattern, NotFoundError } from "@/shared/lib/action-utils"
import type {
  Chapter,
  KnowledgePoint,
  Textbook,
} from "./types"
import type {
  CreateChapterInput,
  CreateKnowledgePointInput,
  CreatePrerequisiteInput,
  CreateTextbookInput,
  DeletePrerequisiteInput,
  UpdateChapterContentInput,
  UpdateKnowledgePointInput,
  UpdateTextbookInput,
} from "./schema"
import {
  buildChapterTree,
  findChapterById,
  normalizeOptional,
  sortChapters,
} from "./utils"

export { buildChapterTree, findChapterById, normalizeOptional, sortChapters }

/**
 * 数据范围过滤参数。
 * 学生端应传入 grade 按年级过滤；教师/admin 端不传则返回全量。
 */
export interface TextbookQueryScope {
  /** 按年级过滤（学生端传入学生所在年级） */
  grade?: string
}

export const getTextbooks = cache(async (query?: string, subject?: string, grade?: string): Promise<Textbook[]> => {
  const conditions: SQL[] = []

  const q = query?.trim()
  if (q) {
    const needle = `%${escapeLikePattern(q)}%`
    const nameCond = or(
      like(textbooks.title, needle),
      like(textbooks.subject, needle),
      like(textbooks.grade, needle),
      like(textbooks.publisher, needle)
    )
    if (nameCond) conditions.push(nameCond)
  }

  const s = subject?.trim()
  if (s && s !== "all") conditions.push(eq(textbooks.subject, s))

  const g = grade?.trim()
  if (g && g !== "all") conditions.push(eq(textbooks.grade, g))

  const rows = await db
    .select({
      id: textbooks.id,
      title: textbooks.title,
      subject: textbooks.subject,
      grade: textbooks.grade,
      publisher: textbooks.publisher,
      createdAt: textbooks.createdAt,
      updatedAt: textbooks.updatedAt,
      chaptersCount: sql<number>`COUNT(${chapters.id})`,
    })
    .from(textbooks)
    .leftJoin(chapters, eq(chapters.textbookId, textbooks.id))
    .where(conditions.length ? and(...conditions) : undefined)
    .groupBy(textbooks.id, textbooks.title, textbooks.subject, textbooks.grade, textbooks.publisher, textbooks.createdAt, textbooks.updatedAt)
    .orderBy(asc(textbooks.title), asc(textbooks.subject), asc(textbooks.grade))

  return rows.map((r) => ({
    id: r.id,
    title: r.title,
    subject: r.subject,
    grade: r.grade,
    publisher: r.publisher,
    createdAt: r.createdAt,
    updatedAt: r.updatedAt,
    _count: { chapters: Number(r.chaptersCount ?? 0) },
  }))
})

export const getTextbookById = cache(async (id: string): Promise<Textbook | undefined> => {
  const [row] = await db
    .select({
      id: textbooks.id,
      title: textbooks.title,
      subject: textbooks.subject,
      grade: textbooks.grade,
      publisher: textbooks.publisher,
      createdAt: textbooks.createdAt,
      updatedAt: textbooks.updatedAt,
      chaptersCount: sql<number>`COUNT(${chapters.id})`,
    })
    .from(textbooks)
    .leftJoin(chapters, eq(chapters.textbookId, textbooks.id))
    .where(eq(textbooks.id, id))
    .groupBy(textbooks.id, textbooks.title, textbooks.subject, textbooks.grade, textbooks.publisher, textbooks.createdAt, textbooks.updatedAt)
    .limit(1)

  if (!row) return undefined

  return {
    id: row.id,
    title: row.title,
    subject: row.subject,
    grade: row.grade,
    publisher: row.publisher,
    createdAt: row.createdAt,
    updatedAt: row.updatedAt,
    _count: { chapters: Number(row.chaptersCount ?? 0) },
  }
})

export const getChaptersByTextbookId = cache(async (textbookId: string): Promise<Chapter[]> => {
  const rows = await db
    .select({
      id: chapters.id,
      textbookId: chapters.textbookId,
      title: chapters.title,
      order: chapters.order,
      parentId: chapters.parentId,
      content: chapters.content,
      createdAt: chapters.createdAt,
      updatedAt: chapters.updatedAt,
    })
    .from(chapters)
    .where(eq(chapters.textbookId, textbookId))
    .orderBy(asc(chapters.order), asc(chapters.title))

  return buildChapterTree(
    rows.map((r) => ({
      id: r.id,
      textbookId: r.textbookId,
      title: r.title,
      order: r.order,
      parentId: r.parentId,
      content: r.content ?? null,
      createdAt: r.createdAt,
      updatedAt: r.updatedAt,
    }))
  )
})

export async function createTextbook(data: CreateTextbookInput): Promise<Textbook> {
  const id = createId()
  const now = new Date()

  const row = {
    id,
    title: data.title.trim(),
    subject: data.subject.trim(),
    grade: normalizeOptional(data.grade),
    publisher: normalizeOptional(data.publisher),
    createdAt: now,
    updatedAt: now,
  }

  await db.insert(textbooks).values(row)

  return {
    ...row,
    _count: { chapters: 0 },
  }
}

export async function updateTextbook(data: UpdateTextbookInput): Promise<Textbook> {
  await db
    .update(textbooks)
    .set({
      title: data.title.trim(),
      subject: data.subject.trim(),
      grade: normalizeOptional(data.grade),
      publisher: normalizeOptional(data.publisher),
    })
    .where(eq(textbooks.id, data.id))

  const updated = await getTextbookById(data.id)
  if (!updated) throw new NotFoundError("教材")
  return updated
}

export async function deleteTextbook(id: string): Promise<void> {
  await db.delete(textbooks).where(eq(textbooks.id, id))
}

export async function createChapter(data: CreateChapterInput): Promise<Chapter> {
  const id = createId()
  const now = new Date()

  const row = {
    id,
    textbookId: data.textbookId,
    title: data.title.trim(),
    order: data.order ?? 0,
    parentId: data.parentId ?? null,
    content: "",
    createdAt: now,
    updatedAt: now,
  }

  await db.insert(chapters).values(row)

  return {
    id: row.id,
    textbookId: row.textbookId,
    title: row.title,
    order: row.order,
    parentId: row.parentId,
    content: row.content,
    createdAt: row.createdAt,
    updatedAt: row.updatedAt,
    children: [],
  }
}

export async function updateChapterContent(data: UpdateChapterContentInput): Promise<Chapter> {
  await db.update(chapters).set({ content: data.content }).where(eq(chapters.id, data.chapterId))

  const [row] = await db
    .select({
      id: chapters.id,
      textbookId: chapters.textbookId,
      title: chapters.title,
      order: chapters.order,
      parentId: chapters.parentId,
      content: chapters.content,
      createdAt: chapters.createdAt,
      updatedAt: chapters.updatedAt,
    })
    .from(chapters)
    .where(eq(chapters.id, data.chapterId))
    .limit(1)

  if (!row) throw new NotFoundError("章节")

  return {
    id: row.id,
    textbookId: row.textbookId,
    title: row.title,
    order: row.order,
    parentId: row.parentId,
    content: row.content ?? null,
    createdAt: row.createdAt,
    updatedAt: row.updatedAt,
    children: [],
  }
}

export async function deleteChapter(id: string): Promise<void> {
  const [target] = await db
    .select({ id: chapters.id, textbookId: chapters.textbookId })
    .from(chapters)
    .where(eq(chapters.id, id))
    .limit(1)

  if (!target) return

  const all = await db
    .select({ id: chapters.id, parentId: chapters.parentId })
    .from(chapters)
    .where(eq(chapters.textbookId, target.textbookId))

  const childrenByParent = new Map<string, string[]>()
  for (const ch of all) {
    if (!ch.parentId) continue
    const arr = childrenByParent.get(ch.parentId) ?? []
    arr.push(ch.id)
    childrenByParent.set(ch.parentId, arr)
  }

  const idsToDelete: string[] = []
  const stack: string[] = [id]
  const seen = new Set<string>()
  while (stack.length) {
    const cur = stack.pop()
    if (!cur) break
    if (seen.has(cur)) continue
    seen.add(cur)
    idsToDelete.push(cur)
    const kids = childrenByParent.get(cur)
    if (kids) stack.push(...kids)
  }

  // P0 数据完整性修复：先查询被删章节下的所有知识点 ID，用于清理孤儿前置依赖记录
  const kpsInChapters = await db
    .select({ id: knowledgePoints.id })
    .from(knowledgePoints)
    .where(inArray(knowledgePoints.chapterId, idsToDelete))
  const kpIds = kpsInChapters.map((k) => k.id)

  await db.transaction(async (tx) => {
    // P0 修复：清理 knowledgePointPrerequisites 表中引用被删知识点的孤儿记录
    // 包括作为 knowledgePointId 和作为 prerequisiteKpId 的记录
    if (kpIds.length > 0) {
      await tx
        .delete(knowledgePointPrerequisites)
        .where(
          or(
            inArray(knowledgePointPrerequisites.knowledgePointId, kpIds),
            inArray(knowledgePointPrerequisites.prerequisiteKpId, kpIds),
          ),
        )
    }
    await tx.delete(knowledgePoints).where(inArray(knowledgePoints.chapterId, idsToDelete))
    await tx.delete(chapters).where(inArray(chapters.id, idsToDelete))
  })
}

export const getKnowledgePointsByChapterId = cache(async (chapterId: string): Promise<KnowledgePoint[]> => {
  const rows = await db
    .select({
      id: knowledgePoints.id,
      name: knowledgePoints.name,
      description: knowledgePoints.description,
      parentId: knowledgePoints.parentId,
      chapterId: knowledgePoints.chapterId,
      level: knowledgePoints.level,
      order: knowledgePoints.order,
    })
    .from(knowledgePoints)
    .where(eq(knowledgePoints.chapterId, chapterId))
    .orderBy(asc(knowledgePoints.order), asc(knowledgePoints.name))

  return rows.map((r) => ({
    id: r.id,
    name: r.name,
    description: r.description ?? null,
    parentId: r.parentId ?? null,
    chapterId: r.chapterId ?? undefined,
    level: r.level ?? 0,
    order: r.order ?? 0,
  }))
})

export const getKnowledgePointsByTextbookId = cache(async (textbookId: string): Promise<KnowledgePoint[]> => {
  const rows = await db
    .select({
      id: knowledgePoints.id,
      name: knowledgePoints.name,
      description: knowledgePoints.description,
      parentId: knowledgePoints.parentId,
      chapterId: knowledgePoints.chapterId,
      level: knowledgePoints.level,
      order: knowledgePoints.order,
    })
    .from(knowledgePoints)
    .innerJoin(chapters, eq(chapters.id, knowledgePoints.chapterId))
    .where(eq(chapters.textbookId, textbookId))
    .orderBy(asc(chapters.order), asc(knowledgePoints.order), asc(knowledgePoints.name))

  return rows.map((r) => ({
    id: r.id,
    name: r.name,
    description: r.description ?? null,
    parentId: r.parentId ?? null,
    chapterId: r.chapterId ?? undefined,
    level: r.level ?? 0,
    order: r.order ?? 0,
  }))
})

export async function createKnowledgePoint(data: CreateKnowledgePointInput): Promise<void> {
  await db.insert(knowledgePoints).values({
    id: createId(),
    name: data.name,
    description: data.description,
    anchorText: data.anchorText,
    chapterId: data.chapterId,
    parentId: data.parentId,
    level: 0, // Default level
    order: 0, // Default order
  })
}

export async function updateKnowledgePoint(data: UpdateKnowledgePointInput): Promise<void> {
  await db
    .update(knowledgePoints)
    .set({
      name: data.name,
      description: data.description,
      anchorText: data.anchorText,
    })
    .where(eq(knowledgePoints.id, data.id))
}

export async function deleteKnowledgePoint(id: string): Promise<void> {
  await db.delete(knowledgePoints).where(eq(knowledgePoints.id, id))
}

export async function reorderChapters(chapterId: string, newIndex: number, parentId: string | null): Promise<void> {
  const [target] = await db.select().from(chapters).where(eq(chapters.id, chapterId)).limit(1)
  if (!target) throw new NotFoundError("章节")

  const siblings = await db
    .select()
    .from(chapters)
    .where(
      and(
        eq(chapters.textbookId, target.textbookId),
        parentId ? eq(chapters.parentId, parentId) : isNull(chapters.parentId)
      )
    )
    .orderBy(asc(chapters.order))

  const currentSiblings = siblings.filter((c) => c.id !== chapterId)
  currentSiblings.splice(newIndex, 0, target)

  await db.transaction(async (tx) => {
    for (let i = 0; i < currentSiblings.length; i++) {
      const ch = currentSiblings[i]
      if (ch.order !== i || (ch.id === chapterId && ch.parentId !== parentId)) {
        await tx
          .update(chapters)
          .set({
            order: i,
            parentId: ch.id === chapterId ? parentId : ch.parentId
          })
          .where(eq(chapters.id, ch.id))
      }
    }
  })
}

export type TextbooksDashboardStats = {
  textbookCount: number
  chapterCount: number
}

export const getTextbooksDashboardStats = cache(async (): Promise<TextbooksDashboardStats> => {
  const [textbookCountRow, chapterCountRow] = await Promise.all([
    db.select({ value: count() }).from(textbooks),
    db.select({ value: count() }).from(chapters),
  ])
  return {
    textbookCount: Number(textbookCountRow[0]?.value ?? 0),
    chapterCount: Number(chapterCountRow[0]?.value ?? 0),
  }
})

// ---------------------------------------------------------------------------
// 资源归属校验（P0-4）
// ---------------------------------------------------------------------------

/**
 * 校验章节是否属于指定教材。
 *
 * 用于 Server Action 二次校验，防止越权操作其他教材的章节。
 *
 * @returns true 表示归属一致
 */
export async function verifyChapterBelongsToTextbook(
  chapterId: string,
  textbookId: string
): Promise<boolean> {
  const [row] = await db
    .select({ textbookId: chapters.textbookId })
    .from(chapters)
    .where(eq(chapters.id, chapterId))
    .limit(1)

  if (!row) return false
  return row.textbookId === textbookId
}

/**
 * 校验知识点是否属于指定教材（通过 chapter → textbook 关联）。
 *
 * 用于 Server Action 二次校验，防止越权操作其他教材的知识点。
 */
export async function verifyKnowledgePointBelongsToTextbook(
  kpId: string,
  textbookId: string
): Promise<boolean> {
  const [row] = await db
    .select({ textbookId: chapters.textbookId })
    .from(knowledgePoints)
    .innerJoin(chapters, eq(chapters.id, knowledgePoints.chapterId))
    .where(eq(knowledgePoints.id, kpId))
    .limit(1)

  if (!row) return false
  return row.textbookId === textbookId
}

// ---------------------------------------------------------------------------
// 带 scope 的查询（P1-1 数据范围过滤）
// ---------------------------------------------------------------------------

/**
 * 按数据范围获取教材列表。
 *
 * 学生端应传入 `scope.grade` 按年级过滤，避免跨年级越权读取。
 */
export const getTextbooksWithScope = cache(
  async (
    query?: string,
    subject?: string,
    grade?: string,
    scope?: TextbookQueryScope
  ): Promise<Textbook[]> => {
    const conditions: SQL[] = []

    const q = query?.trim()
    if (q) {
      const needle = `%${escapeLikePattern(q)}%`
      const nameCond = or(
        like(textbooks.title, needle),
        like(textbooks.subject, needle),
        like(textbooks.grade, needle),
        like(textbooks.publisher, needle)
      )
      if (nameCond) conditions.push(nameCond)
    }

    const s = subject?.trim()
    if (s && s !== "all") conditions.push(eq(textbooks.subject, s))

    // URL 参数 grade（用户筛选）
    const g = grade?.trim()
    if (g && g !== "all") conditions.push(eq(textbooks.grade, g))

    // scope.grade（数据范围过滤，学生端强制按年级过滤）
    const scopeGrade = scope?.grade?.trim()
    if (scopeGrade) conditions.push(eq(textbooks.grade, scopeGrade))

    const rows = await db
      .select({
        id: textbooks.id,
        title: textbooks.title,
        subject: textbooks.subject,
        grade: textbooks.grade,
        publisher: textbooks.publisher,
        createdAt: textbooks.createdAt,
        updatedAt: textbooks.updatedAt,
        chaptersCount: sql<number>`COUNT(${chapters.id})`,
      })
      .from(textbooks)
      .leftJoin(chapters, eq(chapters.textbookId, textbooks.id))
      .where(conditions.length ? and(...conditions) : undefined)
      .groupBy(
        textbooks.id,
        textbooks.title,
        textbooks.subject,
        textbooks.grade,
        textbooks.publisher,
        textbooks.createdAt,
        textbooks.updatedAt
      )
      .orderBy(asc(textbooks.title), asc(textbooks.subject), asc(textbooks.grade))

    return rows.map((r) => ({
      id: r.id,
      title: r.title,
      subject: r.subject,
      grade: r.grade,
      publisher: r.publisher,
      createdAt: r.createdAt,
      updatedAt: r.updatedAt,
      _count: { chapters: Number(r.chaptersCount ?? 0) },
    }))
  }
)

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

export type KnowledgePointOption = {
  id: string
  name: string
  chapterId: string | null
  chapterTitle: string | null
  textbookId: string | null
  textbookTitle: string | null
  subject: string | null
  grade: string | null
}

/**
 * 获取所有知识点选项（含章节和教材信息）。
 * 供 questions 模块跨模块调用使用，避免直接查询 textbooks/chapters/knowledgePoints 表。
 */
export const getKnowledgePointOptions = cache(async (): Promise<KnowledgePointOption[]> => {
  const rows = await db
    .select({
      id: knowledgePoints.id,
      name: knowledgePoints.name,
      chapterId: chapters.id,
      chapterTitle: chapters.title,
      textbookId: textbooks.id,
      textbookTitle: textbooks.title,
      subject: textbooks.subject,
      grade: textbooks.grade,
    })
    .from(knowledgePoints)
    .leftJoin(chapters, eq(chapters.id, knowledgePoints.chapterId))
    .leftJoin(textbooks, eq(textbooks.id, chapters.textbookId))
    .orderBy(
      asc(textbooks.title),
      asc(chapters.order),
      asc(chapters.title),
      asc(knowledgePoints.order),
      asc(knowledgePoints.name)
    )

  return rows.map((row) => ({
    id: row.id,
    name: row.name,
    chapterId: row.chapterId ?? null,
    chapterTitle: row.chapterTitle ?? null,
    textbookId: row.textbookId ?? null,
    textbookTitle: row.textbookTitle ?? null,
    subject: row.subject ?? null,
    grade: row.grade ?? null,
  }))
})

// ===== Prerequisite CRUD =====

export async function createPrerequisite(data: CreatePrerequisiteInput): Promise<void> {
  await db.insert(knowledgePointPrerequisites).values({
    knowledgePointId: data.knowledgePointId,
    prerequisiteKpId: data.prerequisiteKpId,
  })
}

export async function deletePrerequisite(data: DeletePrerequisiteInput): Promise<void> {
  await db
    .delete(knowledgePointPrerequisites)
    .where(and(
      eq(knowledgePointPrerequisites.knowledgePointId, data.knowledgePointId),
      eq(knowledgePointPrerequisites.prerequisiteKpId, data.prerequisiteKpId),
    ))
}

/**
 * 获取教材下所有知识点的前置依赖边列表。
 * 用于循环检测。
 */
export async function getPrerequisiteEdgesForTextbook(
  textbookId: string,
): Promise<Array<[string, string]>> {
  const rows = await db
    .select({
      knowledgePointId: knowledgePointPrerequisites.knowledgePointId,
      prerequisiteKpId: knowledgePointPrerequisites.prerequisiteKpId,
    })
    .from(knowledgePointPrerequisites)
    .innerJoin(knowledgePoints, eq(knowledgePoints.id, knowledgePointPrerequisites.knowledgePointId))
    .innerJoin(chapters, eq(chapters.id, knowledgePoints.chapterId))
    .where(eq(chapters.textbookId, textbookId))

  return rows.map((r): [string, string] => [r.knowledgePointId, r.prerequisiteKpId])
}
