import "server-only"

import { cache } from "react"
import { createId } from "@paralleldrive/cuid2"
import { and, asc, desc, eq, inArray, type SQL } from "drizzle-orm"

import { db } from "@/shared/db"
import { coursePlanItems, coursePlans } from "@/shared/db/schema"
import { safeParseDate } from "@/shared/lib/action-utils"
import type {
  CoursePlan,
  CoursePlanItem,
  CoursePlanListItem,
  CoursePlanQueryScope,
  CoursePlanWithItems,
  GetCoursePlansParams,
  GradeCoursePlanProgressItem,
  GradeCoursePlanProgressResult,
  ReorderCoursePlanItemInput,
} from "./types"
import type {
  CreateCoursePlanInput,
  CreateCoursePlanItemInput,
  UpdateCoursePlanInput,
  UpdateCoursePlanItemInput,
} from "./schema"

const toIso = (d: Date | null | undefined): string | null =>
  d ? d.toISOString() : null

const toIsoRequired = (d: Date): string => d.toISOString()

type PlanRow = typeof coursePlans.$inferSelect

const mapPlanRow = (
  row: PlanRow,
  enrichment: {
    className: string | null
    subjectName: string | null
    teacherName: string | null
  }
): CoursePlanListItem => ({
  id: row.id,
  classId: row.classId,
  subjectId: row.subjectId,
  teacherId: row.teacherId,
  academicYearId: row.academicYearId,
  semester: row.semester,
  totalHours: Number(row.totalHours),
  completedHours: Number(row.completedHours),
  weeklyHours: Number(row.weeklyHours),
  startDate: toIso(row.startDate),
  endDate: toIso(row.endDate),
  syllabus: row.syllabus,
  objectives: row.objectives,
  status: row.status,
  createdBy: row.createdBy,
  createdAt: toIsoRequired(row.createdAt),
  updatedAt: toIsoRequired(row.updatedAt),
  className: enrichment.className,
  subjectName: enrichment.subjectName,
  teacherName: enrichment.teacherName,
})

type ItemRow = typeof coursePlanItems.$inferSelect

const mapItemRow = (row: ItemRow): CoursePlanItem => ({
  id: row.id,
  planId: row.planId,
  week: Number(row.week),
  topic: row.topic,
  content: row.content,
  hours: Number(row.hours),
  textbookChapter: row.textbookChapter,
  notes: row.notes,
  isCompleted: Boolean(row.isCompleted),
  completedAt: toIso(row.completedAt),
  createdAt: toIsoRequired(row.createdAt),
  updatedAt: toIsoRequired(row.updatedAt),
})

/**
 * 批量解析班级/科目/教师名称（通过对方 data-access，不直接 JOIN 其他模块表）。
 * 解耦 P1-1：course-plans 仅查询自己的 course_plans 表，
 * 名称解析委托给 classes/school/users 模块的 data-access。
 */
async function enrichPlanRows(
  rows: PlanRow[]
): Promise<CoursePlanListItem[]> {
  if (rows.length === 0) return []

  const { getClassNamesByIds } = await import("@/modules/classes/data-access")
  const { getSubjectNameMapByIds } = await import("@/modules/school/data-access")
  const { getUserNamesByIds } = await import("@/modules/users/data-access")

  const classIds = Array.from(new Set(rows.map((r) => r.classId)))
  const subjectIds = Array.from(new Set(rows.map((r) => r.subjectId)))
  const teacherIds = Array.from(new Set(rows.map((r) => r.teacherId)))

  const [classNameMap, subjectNameMap, teacherNameMap] = await Promise.all([
    getClassNamesByIds(classIds),
    getSubjectNameMapByIds(subjectIds),
    getUserNamesByIds(teacherIds),
  ])

  return rows.map((row) =>
    mapPlanRow(row, {
      className: classNameMap.get(row.classId) ?? null,
      subjectName: subjectNameMap.get(row.subjectId) ?? null,
      teacherName: teacherNameMap.get(row.teacherId)?.name ?? null,
    })
  )
}

/**
 * 单条计划的名称解析（用于详情查询）。
 */
async function enrichPlanRow(row: PlanRow): Promise<CoursePlanListItem> {
  const enriched = await enrichPlanRows([row])
  return enriched[0]
}

const buildScopeCondition = (
  scope?: CoursePlanQueryScope
): SQL[] => {
  const conditions: SQL[] = []
  if (!scope) return conditions

  // 管理员拥有全局视图，不加过滤
  if (scope.isAdmin) return conditions

  // 教师视角：仅查看自己负责的计划
  if (scope.teacherId) {
    conditions.push(eq(coursePlans.teacherId, scope.teacherId))
  }

  // 班级范围过滤（学生/家长/年级主任）
  if (scope.classIds && scope.classIds.length > 0) {
    conditions.push(inArray(coursePlans.classId, scope.classIds))
  }

  // 若非 admin 且未提供任何过滤条件，默认仅返回自己创建的
  if (!scope.isAdmin && !scope.teacherId && (!scope.classIds || scope.classIds.length === 0)) {
    conditions.push(eq(coursePlans.createdBy, scope.userId))
  }

  return conditions
}

export const getCoursePlans = cache(
  async (params?: GetCoursePlansParams, scope?: CoursePlanQueryScope): Promise<CoursePlanListItem[]> => {
    try {
      const conditions: SQL[] = [...buildScopeCondition(scope)]
      if (params?.classId) conditions.push(eq(coursePlans.classId, params.classId))
      if (params?.teacherId) conditions.push(eq(coursePlans.teacherId, params.teacherId))
      if (params?.subjectId) conditions.push(eq(coursePlans.subjectId, params.subjectId))
      if (params?.status)
        conditions.push(eq(coursePlans.status, params.status))

      const query = db.select().from(coursePlans)
      const rows = await (conditions.length > 0
        ? query.where(and(...conditions))
        : query
      ).orderBy(desc(coursePlans.createdAt))

      return await enrichPlanRows(rows)
    } catch (error) {
      console.error("getCoursePlans failed:", error)
      return []
    }
  }
)

export const getCoursePlanById = cache(
  async (id: string, scope?: CoursePlanQueryScope): Promise<CoursePlanWithItems | null> => {
    try {
      const conditions: SQL[] = [eq(coursePlans.id, id), ...buildScopeCondition(scope)]
      const [planRow] = await db
        .select()
        .from(coursePlans)
        .where(and(...conditions))
        .limit(1)

      if (!planRow) return null

      const enriched = await enrichPlanRow(planRow)

      const itemRows = await db
        .select()
        .from(coursePlanItems)
        .where(eq(coursePlanItems.planId, id))
        .orderBy(asc(coursePlanItems.week), asc(coursePlanItems.createdAt))

      return {
        ...enriched,
        items: itemRows.map(mapItemRow),
      }
    } catch (error) {
      console.error("getCoursePlanById failed:", error)
      return null
    }
  }
)

export async function createCoursePlan(
  data: CreateCoursePlanInput,
  createdBy: string
): Promise<string> {
  const id = createId()
  await db.insert(coursePlans).values({
    id,
    classId: data.classId,
    subjectId: data.subjectId,
    teacherId: data.teacherId,
    academicYearId: data.academicYearId,
    semester: data.semester,
    totalHours: data.totalHours,
    completedHours: 0,
    weeklyHours: data.weeklyHours,
    startDate: data.startDate ? safeParseDate(data.startDate, "开始日期") : null,
    endDate: data.endDate ? safeParseDate(data.endDate, "结束日期") : null,
    syllabus: data.syllabus,
    objectives: data.objectives,
    status: data.status,
    createdBy,
  })
  return id
}

export async function updateCoursePlan(
  id: string,
  data: Partial<UpdateCoursePlanInput>
): Promise<void> {
  const update: Partial<typeof coursePlans.$inferSelect> = {}
  if (data.classId !== undefined) update.classId = data.classId
  if (data.subjectId !== undefined) update.subjectId = data.subjectId
  if (data.teacherId !== undefined) update.teacherId = data.teacherId
  if (data.academicYearId !== undefined) update.academicYearId = data.academicYearId
  if (data.semester !== undefined) update.semester = data.semester
  if (data.totalHours !== undefined) update.totalHours = data.totalHours
  if (data.completedHours !== undefined) update.completedHours = data.completedHours
  if (data.weeklyHours !== undefined) update.weeklyHours = data.weeklyHours
  if (data.startDate !== undefined)
    update.startDate = data.startDate ? safeParseDate(data.startDate, "开始日期") : null
  if (data.endDate !== undefined)
    update.endDate = data.endDate ? safeParseDate(data.endDate, "结束日期") : null
  if (data.syllabus !== undefined) update.syllabus = data.syllabus
  if (data.objectives !== undefined) update.objectives = data.objectives
  if (data.status !== undefined) update.status = data.status

  if (Object.keys(update).length === 0) return

  await db.update(coursePlans).set(update).where(eq(coursePlans.id, id))
}

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

export async function createCoursePlanItem(
  data: CreateCoursePlanItemInput
): Promise<string> {
  const id = createId()
  await db.insert(coursePlanItems).values({
    id,
    planId: data.planId,
    week: data.week,
    topic: data.topic,
    content: data.content,
    hours: data.hours,
    textbookChapter: data.textbookChapter,
    notes: data.notes,
  })
  return id
}

export async function updateCoursePlanItem(
  id: string,
  data: Partial<UpdateCoursePlanItemInput>
): Promise<void> {
  const update: Partial<typeof coursePlanItems.$inferSelect> = {}
  if (data.week !== undefined) update.week = data.week
  if (data.topic !== undefined) update.topic = data.topic
  if (data.content !== undefined) update.content = data.content
  if (data.hours !== undefined) update.hours = data.hours
  if (data.textbookChapter !== undefined) update.textbookChapter = data.textbookChapter
  if (data.notes !== undefined) update.notes = data.notes
  if (data.isCompleted !== undefined) update.isCompleted = data.isCompleted
  if (data.completedAt !== undefined)
    update.completedAt = data.completedAt ? safeParseDate(data.completedAt, "完成日期") : null

  if (Object.keys(update).length === 0) return

  await db.update(coursePlanItems).set(update).where(eq(coursePlanItems.id, id))
}

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

export async function reorderCoursePlanItems(
  planId: string,
  items: ReorderCoursePlanItemInput[]
): Promise<void> {
  if (items.length === 0) return

  const itemIds = items.map((i) => i.id)
  const [existing] = await db
    .select({ id: coursePlanItems.id })
    .from(coursePlanItems)
    .where(and(eq(coursePlanItems.planId, planId), inArray(coursePlanItems.id, itemIds)))
    .limit(1)

  if (!existing) return

  await Promise.all(
    items.map((item) =>
      db
        .update(coursePlanItems)
        .set({ week: item.week })
        .where(eq(coursePlanItems.id, item.id))
    )
  )
}

/**
 * 批量标记周计划条目完成状态（P2-4 批量操作）。
 */
export async function bulkUpdateItemCompleted(
  itemIds: string[],
  completed: boolean
): Promise<number> {
  if (itemIds.length === 0) return 0
  // schema 列为 Date 类型，使用 Date 对象而非字符串
  const completedAt = completed ? new Date() : null
  await db
    .update(coursePlanItems)
    .set({ isCompleted: completed, completedAt })
    .where(inArray(coursePlanItems.id, itemIds))
  return itemIds.length
}

/**
 * 复制课程计划到其他班级（P2-4 批量操作）。
 * 复制基本信息（不含 completedHours）和所有周计划条目。
 */
export async function copyCoursePlanToClasses(
  sourcePlanId: string,
  targetClassIds: string[]
): Promise<string[]> {
  if (targetClassIds.length === 0) return []

  const [source] = await db
    .select()
    .from(coursePlans)
    .where(eq(coursePlans.id, sourcePlanId))
    .limit(1)

  if (!source) return []

  const sourceItems = await db
    .select()
    .from(coursePlanItems)
    .where(eq(coursePlanItems.planId, sourcePlanId))

  const createdIds: string[] = []

  for (const classId of targetClassIds) {
    const newPlanId = createId()
    await db.insert(coursePlans).values({
      id: newPlanId,
      classId,
      subjectId: source.subjectId,
      teacherId: source.teacherId,
      academicYearId: source.academicYearId,
      semester: source.semester,
      totalHours: source.totalHours,
      completedHours: 0,
      weeklyHours: source.weeklyHours,
      startDate: source.startDate,
      endDate: source.endDate,
      syllabus: source.syllabus,
      objectives: source.objectives,
      status: "planning",
      createdBy: source.createdBy,
    })

    for (const item of sourceItems) {
      const newItemId = createId()
      await db.insert(coursePlanItems).values({
        id: newItemId,
        planId: newPlanId,
        week: item.week,
        topic: item.topic,
        content: item.content,
        hours: item.hours,
        textbookChapter: item.textbookChapter,
        notes: item.notes,
      })
    }

    createdIds.push(newPlanId)
  }

  return createdIds
}

export type { CoursePlan, CoursePlanItem, CoursePlanWithItems }

/**
 * 年级仪表盘 - 维度4：获取年级下所有班级的教学计划进度。
 * 通过 getClassesByGradeId 获取年级下所有班级，再用 inArray 查询 course_plans，
 * 关联 course_plan_items 统计条目完成情况。
 * 名称解析通过 data-access 批量查询，不 JOIN 其他模块表。
 */
export const getGradeCoursePlanProgress = cache(
  async (params: { gradeId: string }): Promise<GradeCoursePlanProgressResult> => {
    const { getClassesByGradeId } = await import("@/modules/classes/data-access")
    const classRows = await getClassesByGradeId(params.gradeId)

    const empty: GradeCoursePlanProgressResult = {
      gradeId: params.gradeId,
      overall: { totalPlans: 0, totalHours: 0, completedHours: 0, progressRate: 0, activePlans: 0, completedPlans: 0 },
      items: [],
    }

    if (classRows.length === 0) return empty

    const classIds = classRows.map((c) => c.id)

    // 查询年级下所有教学计划（仅 course_plans 表）
    const planRows = await db
      .select()
      .from(coursePlans)
      .where(inArray(coursePlans.classId, classIds))
      .orderBy(desc(coursePlans.createdAt))

    if (planRows.length === 0) return empty

    const planIds = planRows.map((p) => p.id)

    // 查询所有计划的周次条目，统计完成情况
    const itemRows = await db
      .select({
        planId: coursePlanItems.planId,
        isCompleted: coursePlanItems.isCompleted,
      })
      .from(coursePlanItems)
      .where(inArray(coursePlanItems.planId, planIds))

    const itemStatsByPlan = new Map<string, { total: number; completed: number }>()
    for (const it of itemRows) {
      const entry = itemStatsByPlan.get(it.planId) ?? { total: 0, completed: 0 }
      entry.total += 1
      if (it.isCompleted) entry.completed += 1
      itemStatsByPlan.set(it.planId, entry)
    }

    // 批量解析名称
    const enriched = await enrichPlanRows(planRows)

    const classNameMap = new Map(classRows.map((c) => [c.id, c.name] as const))

    const items: GradeCoursePlanProgressItem[] = enriched.map((p) => {
      const totalHours = p.totalHours
      const completedHours = p.completedHours
      const progressRate = totalHours > 0
        ? Math.round((completedHours / totalHours) * 1000) / 10
        : 0
      const itemStats = itemStatsByPlan.get(p.id) ?? { total: 0, completed: 0 }
      return {
        planId: p.id,
        classId: p.classId,
        className: classNameMap.get(p.classId) ?? p.className,
        subjectId: p.subjectId,
        subjectName: p.subjectName,
        teacherName: p.teacherName,
        semester: p.semester,
        totalHours,
        completedHours,
        progressRate,
        status: p.status,
        itemCount: itemStats.total,
        completedItemCount: itemStats.completed,
      }
    })

    const totalHours = items.reduce((sum, i) => sum + i.totalHours, 0)
    const completedHours = items.reduce((sum, i) => sum + i.completedHours, 0)
    const progressRate = totalHours > 0
      ? Math.round((completedHours / totalHours) * 1000) / 10
      : 0

    return {
      gradeId: params.gradeId,
      overall: {
        totalPlans: items.length,
        totalHours,
        completedHours,
        progressRate,
        activePlans: items.filter((i) => i.status === "active").length,
        completedPlans: items.filter((i) => i.status === "completed").length,
      },
      items,
    }
  }
)
