import "server-only"

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

import { db } from "@/shared/db"
import {
  courseSelections,
  electiveCourses,
} from "@/shared/db/schema"

import type { CourseSelectionStatus } from "./types"

/** 学分上限（K12 选修课学期学分上限，可按需调整） */
const MAX_CREDIT_PER_TERM = 10

/**
 * 构建 lotteryRank 的 CASE SQL 表达式（纯函数，便于测试 SQL 片段结构）。
 */
export function buildLotteryRankCase(ids: string[], startRank: number): SQL {
  const branches = ids.map(
    (id, idx) => sql`WHEN ${id} THEN ${startRank + idx}`
  )
  return sql`CASE ${courseSelections.id} ${sql.join(branches, sql` `)} END`
}

/**
 * 解析课程 schedule 字段为可比较的时间段（纯函数，便于测试）。
 * schedule 格式约定："周一 14:00-15:30" 或 "Mon 14:00-15:30"。
 * 返回 null 表示无法解析（不参与冲突检测）。
 */
export function parseSchedule(schedule: string | null): { day: string; start: string; end: string } | null {
  if (!schedule || schedule.length === 0) return null
  // 匹配 "周X HH:MM-HH:MM" 或 "Day HH:MM-HH:MM"
  const match = schedule.match(/^(周[一二三四五六日天]|[MonTueWedThuFriSatSun]+)\s+(\d{1,2}:\d{2})\s*[-~]\s*(\d{1,2}:\d{2})/i)
  if (!match) return null
  const [, day, start, end] = match
  return { day: day ?? "", start: start ?? "", end: end ?? "" }
}

/**
 * 检测两个时间段是否冲突（纯函数，便于测试）。
 * 仅当 day 相同且时间区间重叠时判定为冲突。
 */
export function isScheduleConflict(
  a: { day: string; start: string; end: string },
  b: { day: string; start: string; end: string }
): boolean {
  // 归一化星期表示（周一/Mon → 1，周二/Tue → 2 ...）
  const normalizeDay = (d: string): string => {
    const dayMap: Record<string, string> = {
      "周一": "1", "周二": "2", "周三": "3", "周四": "4", "周五": "5", "周六": "6", "周日": "7", "周天": "7",
      "mon": "1", "tue": "2", "wed": "3", "thu": "4", "fri": "5", "sat": "6", "sun": "7",
    }
    return dayMap[d.toLowerCase()] ?? d
  }
  if (normalizeDay(a.day) !== normalizeDay(b.day)) return false
  return a.start < b.end && b.start < a.end
}

/**
 * 检测学生选课时间冲突（P2 建议：选课时间冲突检测）。
 * 查询学生已选/已录取的课程，与新课程 schedule 比对。
 */
async function checkScheduleConflict(
  tx: Parameters<Parameters<typeof db.transaction>[0]>[0],
  studentId: string,
  newCourseId: string
): Promise<boolean> {
  const [newCourse] = await tx
    .select({ schedule: electiveCourses.schedule })
    .from(electiveCourses)
    .where(eq(electiveCourses.id, newCourseId))
    .limit(1)
  const newSchedule = parseSchedule(newCourse?.schedule ?? null)
  if (!newSchedule) return false

  const existingCourses = await tx
    .select({
      schedule: electiveCourses.schedule,
    })
    .from(courseSelections)
    .innerJoin(electiveCourses, eq(electiveCourses.id, courseSelections.courseId))
    .where(
      and(
        eq(courseSelections.studentId, studentId),
        inArray(courseSelections.status, ["selected", "enrolled", "waitlist"])
      )
    )

  for (const row of existingCourses) {
    const existing = parseSchedule(row.schedule)
    if (existing && isScheduleConflict(newSchedule, existing)) {
      return true
    }
  }
  return false
}

/**
 * 检测学生学分是否超限（P2 建议：学分上限校验）。
 * 查询学生已选课程的学分总和，加上新课程学分后是否超过上限。
 */
async function checkCreditLimit(
  tx: Parameters<Parameters<typeof db.transaction>[0]>[0],
  studentId: string,
  newCourseId: string
): Promise<{ exceeded: boolean; current: number; max: number }> {
  const [newCourse] = await tx
    .select({ credit: electiveCourses.credit })
    .from(electiveCourses)
    .where(eq(electiveCourses.id, newCourseId))
    .limit(1)
  const newCredit = Number(newCourse?.credit ?? 0)

  const existing = await tx
    .select({
      credit: electiveCourses.credit,
    })
    .from(courseSelections)
    .innerJoin(electiveCourses, eq(electiveCourses.id, courseSelections.courseId))
    .where(
      and(
        eq(courseSelections.studentId, studentId),
        inArray(courseSelections.status, ["selected", "enrolled", "waitlist"])
      )
    )

  const currentCredit = existing.reduce((sum, row) => sum + Number(row.credit ?? 0), 0)
  const total = currentCredit + newCredit
  return {
    exceeded: total > MAX_CREDIT_PER_TERM,
    current: total,
    max: MAX_CREDIT_PER_TERM,
  }
}

export async function runLottery(courseId: string): Promise<{
  enrolled: number
  waitlist: number
}> {
  const [courseRows, selections] = await Promise.all([
    db
      .select()
      .from(electiveCourses)
      .where(eq(electiveCourses.id, courseId))
      .limit(1),
    db
      .select()
      .from(courseSelections)
      .where(
        and(
          eq(courseSelections.courseId, courseId),
          eq(courseSelections.status, "selected")
        )
      )
      .orderBy(asc(courseSelections.priority), asc(courseSelections.selectedAt)),
  ])
  const course = courseRows[0]
  if (!course) throw new Error("Course not found")

  if (selections.length === 0) {
    return { enrolled: 0, waitlist: 0 }
  }

  // Fisher-Yates shuffle: 无偏均匀随机排列，避免 sort(() => Math.random() - 0.5) 的分布偏差
  const shuffled = [...selections]
  for (let i = shuffled.length - 1; i > 0; i--) {
    const j = Math.floor(Math.random() * (i + 1))
    ;[shuffled[i], shuffled[j]] = [shuffled[j], shuffled[i]]
  }
  const capacity = course.capacity
  const now = new Date()

  const enrolledIds: string[] = []
  const waitlistIds: string[] = []
  for (let i = 0; i < shuffled.length; i++) {
    if (i < capacity) {
      enrolledIds.push(shuffled[i].id)
    } else {
      waitlistIds.push(shuffled[i].id)
    }
  }

  const enrolledCount = enrolledIds.length
  const waitlistCount = waitlistIds.length

  await db.transaction(async (tx) => {
    if (enrolledIds.length > 0) {
      await tx
        .update(courseSelections)
        .set({
          status: "enrolled",
          lotteryRank: buildLotteryRankCase(enrolledIds, 1),
          enrolledAt: now,
          updatedAt: now,
        })
        .where(inArray(courseSelections.id, enrolledIds))
    }
    if (waitlistIds.length > 0) {
      await tx
        .update(courseSelections)
        .set({
          status: "waitlist",
          lotteryRank: buildLotteryRankCase(waitlistIds, capacity + 1),
          updatedAt: now,
        })
        .where(inArray(courseSelections.id, waitlistIds))
    }
    await tx
      .update(electiveCourses)
      .set({ enrolledCount, status: "closed", updatedAt: now })
      .where(eq(electiveCourses.id, courseId))
  })

  return { enrolled: enrolledCount, waitlist: waitlistCount }
}

export async function selectCourse(
  courseId: string,
  studentId: string,
  priority?: number
): Promise<{ status: CourseSelectionStatus; message: string }> {
  return db.transaction(async (tx) => {
    // 锁定课程行，防止 FCFS 模式下并发超卖
    const [course] = await tx
      .select()
      .from(electiveCourses)
      .where(eq(electiveCourses.id, courseId))
      .for("update")
      .limit(1)
    if (!course) throw new Error("Course not found")
    if (course.status !== "open") throw new Error("Course selection is not open")

    const now = new Date()
    if (course.selectionStartAt && now < course.selectionStartAt) {
      throw new Error("Selection has not started yet")
    }
    if (course.selectionEndAt && now > course.selectionEndAt) {
      throw new Error("Selection has ended")
    }

    const [existing] = await tx
      .select()
      .from(courseSelections)
      .where(
        and(
          eq(courseSelections.courseId, courseId),
          eq(courseSelections.studentId, studentId),
          inArray(courseSelections.status, ["selected", "enrolled", "waitlist"])
        )
      )
      .limit(1)
    if (existing) throw new Error("Already selected this course")

    // P2 建议：选课时间冲突检测
    const hasConflict = await checkScheduleConflict(tx, studentId, courseId)
    if (hasConflict) {
      throw new Error("Schedule conflicts with your existing courses")
    }

    // P2 建议：学分上限校验
    const creditCheck = await checkCreditLimit(tx, studentId, courseId)
    if (creditCheck.exceeded) {
      throw new Error(`Credit limit exceeded (${creditCheck.current}/${creditCheck.max})`)
    }

    const id = createId()
    let status: CourseSelectionStatus = "selected"
    let enrolledAt: Date | null = null

    if (course.selectionMode === "fcfs" && course.enrolledCount < course.capacity) {
      status = "enrolled"
      enrolledAt = now
      await tx
        .update(electiveCourses)
        .set({
          enrolledCount: course.enrolledCount + 1,
          updatedAt: now,
        })
        .where(eq(electiveCourses.id, courseId))
    } else if (course.selectionMode === "fcfs") {
      status = "waitlist"
    }

    await tx.insert(courseSelections).values({
      id,
      courseId,
      studentId,
      status,
      priority: priority ?? 1,
      selectedAt: now,
      enrolledAt,
    })

    return {
      status,
      message:
        status === "enrolled"
          ? "Enrolled successfully"
          : status === "waitlist"
            ? "Added to waitlist"
            : "Selection submitted",
    }
  })
}

export async function dropCourse(
  courseId: string,
  studentId: string
): Promise<void> {
  await db.transaction(async (tx) => {
    const [existing] = await tx
      .select()
      .from(courseSelections)
      .where(
        and(
          eq(courseSelections.courseId, courseId),
          eq(courseSelections.studentId, studentId),
          inArray(courseSelections.status, ["selected", "enrolled", "waitlist"])
        )
      )
      .limit(1)
    if (!existing) throw new Error("No active selection found")

    // 锁定课程行，确保 enrolledCount 更新与候补递补的原子性
    const [course] = await tx
      .select()
      .from(electiveCourses)
      .where(eq(electiveCourses.id, courseId))
      .for("update")
      .limit(1)

    const now = new Date()
    await tx
      .update(courseSelections)
      .set({ status: "dropped", droppedAt: now, updatedAt: now })
      .where(eq(courseSelections.id, existing.id))

    if (existing.status === "enrolled" && course && course.selectionMode === "fcfs") {
      const newEnrolledCount = Math.max(0, course.enrolledCount - 1)
      await tx
        .update(electiveCourses)
        .set({ enrolledCount: newEnrolledCount, updatedAt: now })
        .where(eq(electiveCourses.id, courseId))

      const [nextWait] = await tx
        .select()
        .from(courseSelections)
        .where(
          and(
            eq(courseSelections.courseId, courseId),
            eq(courseSelections.status, "waitlist")
          )
        )
        .orderBy(asc(courseSelections.priority), asc(courseSelections.selectedAt))
        .limit(1)
      if (nextWait) {
        await tx
          .update(courseSelections)
          .set({
            status: "enrolled",
            enrolledAt: now,
            updatedAt: now,
          })
          .where(eq(courseSelections.id, nextWait.id))
        await tx
          .update(electiveCourses)
          .set({ enrolledCount: newEnrolledCount + 1, updatedAt: now })
          .where(eq(electiveCourses.id, courseId))
      }
    }
  })
}
