import "server-only"

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

import { db } from "@/shared/db"
import { electiveCourses } from "@/shared/db/schema"
import type { DataScope } from "@/shared/types/permissions"
import { safeParseDate } from "@/shared/lib/action-utils"

import type {
  ElectiveCourseWithDetails,
  GetElectiveCoursesParams,
} from "./types"
import type {
  CreateElectiveCourseInput,
  UpdateElectiveCourseInput,
} from "./schema"
import { getCourseDisplayResolver } from "./resolvers"

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

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

const buildScopeFilter = (scope: DataScope, userId?: string): SQL | null => {
  if (scope.type === "all") return null
  if (scope.type === "owned" && userId) return eq(electiveCourses.teacherId, userId)
  if (scope.type === "class_taught" && userId) {
    return eq(electiveCourses.teacherId, userId)
  }
  if (scope.type === "grade_managed") {
    return scope.gradeIds.length > 0
      ? inArray(electiveCourses.gradeId, scope.gradeIds)
      : sql`1=0`
  }
  if (scope.type === "class_members") return null
  if (scope.type === "children") return null
  return sql`1=0`
}

export type CourseCoreRow = typeof electiveCourses.$inferSelect

export const mapCourseRow = (
  r: CourseCoreRow,
  teacherNames: Map<string, string | null>,
  subjectNames: Map<string, string>,
  gradeNames: Map<string, string>
): ElectiveCourseWithDetails => ({
  id: r.id,
  name: r.name,
  subjectId: r.subjectId,
  teacherId: r.teacherId,
  gradeId: r.gradeId,
  description: r.description,
  capacity: r.capacity,
  enrolledCount: r.enrolledCount,
  classroom: r.classroom,
  schedule: r.schedule,
  startDate: r.startDate ? new Date(r.startDate).toISOString().slice(0, 10) : null,
  endDate: r.endDate ? new Date(r.endDate).toISOString().slice(0, 10) : null,
  selectionStartAt: toIso(r.selectionStartAt),
  selectionEndAt: toIso(r.selectionEndAt),
  dropDeadline: toIso(r.dropDeadline),
  status: r.status,
  selectionMode: r.selectionMode,
  credit: String(r.credit),
  createdAt: toIsoRequired(r.createdAt),
  updatedAt: toIsoRequired(r.updatedAt),
  teacherName: r.teacherId ? (teacherNames.get(r.teacherId) ?? null) : null,
  subjectName: r.subjectId ? (subjectNames.get(r.subjectId) ?? null) : null,
  gradeName: r.gradeId ? (gradeNames.get(r.gradeId) ?? null) : null,
})

export const buildCourseSelect = () =>
  db
    .select({
      id: electiveCourses.id,
      name: electiveCourses.name,
      subjectId: electiveCourses.subjectId,
      teacherId: electiveCourses.teacherId,
      gradeId: electiveCourses.gradeId,
      description: electiveCourses.description,
      capacity: electiveCourses.capacity,
      enrolledCount: electiveCourses.enrolledCount,
      classroom: electiveCourses.classroom,
      schedule: electiveCourses.schedule,
      startDate: electiveCourses.startDate,
      endDate: electiveCourses.endDate,
      selectionStartAt: electiveCourses.selectionStartAt,
      selectionEndAt: electiveCourses.selectionEndAt,
      dropDeadline: electiveCourses.dropDeadline,
      status: electiveCourses.status,
      selectionMode: electiveCourses.selectionMode,
      credit: electiveCourses.credit,
      createdAt: electiveCourses.createdAt,
      updatedAt: electiveCourses.updatedAt,
    })
    .from(electiveCourses)

/**
 * 缓存科目/年级选项（单次请求内复用，避免高频访问时重复查询）。
 * 使用 React `cache()` 在同一渲染周期内去重。
 */
const getCachedSubjectOptions = cache(async () => getCourseDisplayResolver().getSubjectOptions())
const getCachedGradeOptions = cache(async () => getCourseDisplayResolver().getGradeOptions())

export const resolveCourseDisplayNames = async (rows: CourseCoreRow[]): Promise<{
  teacherNames: Map<string, string | null>
  subjectNames: Map<string, string>
  gradeNames: Map<string, string>
}> => {
  const resolver = getCourseDisplayResolver()
  const teacherIds = Array.from(new Set(rows.map((r) => r.teacherId).filter((v): v is string => typeof v === "string" && v.length > 0)))
  const [userMap, subjects, grades] = await Promise.all([
    resolver.getUserNamesByIds(teacherIds),
    getCachedSubjectOptions(),
    getCachedGradeOptions(),
  ])

  const teacherNames = new Map<string, string | null>()
  for (const [id, user] of userMap.entries()) {
    teacherNames.set(id, user.name)
  }
  const subjectNames = new Map<string, string>()
  for (const s of subjects) subjectNames.set(s.id, s.name)
  const gradeNames = new Map<string, string>()
  for (const g of grades) gradeNames.set(g.id, g.name)

  return { teacherNames, subjectNames, gradeNames }
}

export const getElectiveCourses = cache(
  async (
    params?: GetElectiveCoursesParams & { scope?: DataScope; currentUserId?: string }
  ): Promise<ElectiveCourseWithDetails[]> => {
    const conditions: SQL[] = []
    if (params?.status)
      conditions.push(
        eq(electiveCourses.status, params.status)
      )
    if (params?.gradeId) conditions.push(eq(electiveCourses.gradeId, params.gradeId))
    if (params?.subjectId)
      conditions.push(eq(electiveCourses.subjectId, params.subjectId))
    if (params?.teacherId)
      conditions.push(eq(electiveCourses.teacherId, params.teacherId))
    if (params?.scope) {
      const scopeFilter = buildScopeFilter(params.scope, params.currentUserId)
      if (scopeFilter) conditions.push(scopeFilter)
    }

    const query = buildCourseSelect()
    const rows = await (conditions.length > 0
      ? query.where(and(...conditions))
      : query
    ).orderBy(desc(electiveCourses.createdAt))

    if (rows.length === 0) return []
    const displayMaps = await resolveCourseDisplayNames(rows)
    return rows.map((r) => mapCourseRow(r, displayMaps.teacherNames, displayMaps.subjectNames, displayMaps.gradeNames))
  }
)

export const getElectiveCourseById = cache(
  async (id: string): Promise<ElectiveCourseWithDetails | null> => {
    const [row] = await buildCourseSelect()
      .where(eq(electiveCourses.id, id))
      .limit(1)
    if (!row) return null
    const displayMaps = await resolveCourseDisplayNames([row])
    return mapCourseRow(row, displayMaps.teacherNames, displayMaps.subjectNames, displayMaps.gradeNames)
  }
)

export async function createElectiveCourse(
  data: CreateElectiveCourseInput,
  teacherId: string
): Promise<string> {
  const id = createId()
  await db.insert(electiveCourses).values({
    id,
    name: data.name,
    subjectId: data.subjectId,
    teacherId: data.teacherId ?? teacherId,
    gradeId: data.gradeId,
    description: data.description,
    capacity: data.capacity,
    enrolledCount: 0,
    classroom: data.classroom,
    schedule: data.schedule,
    startDate: data.startDate ? safeParseDate(data.startDate, "开始日期") : null,
    endDate: data.endDate ? safeParseDate(data.endDate, "结束日期") : null,
    selectionStartAt: data.selectionStartAt ? safeParseDate(data.selectionStartAt, "选课开始时间") : null,
    selectionEndAt: data.selectionEndAt ? safeParseDate(data.selectionEndAt, "选课结束时间") : null,
    // P2-4：退课截止时间
    dropDeadline: data.dropDeadline ? safeParseDate(data.dropDeadline, "退课截止时间") : null,
    status: "draft",
    selectionMode: data.selectionMode,
    credit: data.credit,
  })
  return id
}

export async function updateElectiveCourse(
  id: string,
  data: Partial<UpdateElectiveCourseInput>
): Promise<void> {
  const update: Partial<typeof electiveCourses.$inferSelect> = {}
  if (data.name !== undefined) update.name = data.name
  if (data.subjectId !== undefined) update.subjectId = data.subjectId
  if (data.teacherId !== undefined) update.teacherId = data.teacherId
  if (data.gradeId !== undefined) update.gradeId = data.gradeId
  if (data.description !== undefined) update.description = data.description
  if (data.capacity !== undefined) update.capacity = data.capacity
  if (data.classroom !== undefined) update.classroom = data.classroom
  if (data.schedule !== undefined) update.schedule = data.schedule
  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.selectionStartAt !== undefined)
    update.selectionStartAt = data.selectionStartAt ? safeParseDate(data.selectionStartAt, "选课开始时间") : null
  if (data.selectionEndAt !== undefined)
    update.selectionEndAt = data.selectionEndAt ? safeParseDate(data.selectionEndAt, "选课结束时间") : null
  // P2-4：退课截止时间
  if (data.dropDeadline !== undefined)
    update.dropDeadline = data.dropDeadline ? safeParseDate(data.dropDeadline, "退课截止时间") : null
  if (data.status !== undefined) update.status = data.status
  if (data.selectionMode !== undefined) update.selectionMode = data.selectionMode
  if (data.credit !== undefined) update.credit = data.credit

  if (Object.keys(update).length === 0) return
  await db.update(electiveCourses).set(update).where(eq(electiveCourses.id, id))
}

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

export async function openSelection(courseId: string): Promise<void> {
  await db
    .update(electiveCourses)
    .set({ status: "open", updatedAt: new Date() })
    .where(eq(electiveCourses.id, courseId))
}

export async function closeSelection(courseId: string): Promise<void> {
  await db
    .update(electiveCourses)
    .set({ status: "closed", updatedAt: new Date() })
    .where(eq(electiveCourses.id, courseId))
}

export type { ElectiveCourseWithDetails }
