import "server-only"

import { cache } from "react"
import { and, asc, desc, eq, sql, type SQL } from "drizzle-orm"

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

import {
  buildCourseSelect,
  mapCourseRow,
  resolveCourseDisplayNames,
  type CourseCoreRow,
} from "./data-access"
import { getStudentGradeResolver, getCourseDisplayResolver } from "./resolvers"
import type {
  CourseSelectionWithDetails,
  ElectiveCourseWithDetails,
} from "./types"

type SelectionCoreRow = {
  id: string
  courseId: string
  studentId: string
  status: (typeof courseSelections.status.enumValues)[number]
  priority: number | null
  selectedAt: Date
  enrolledAt: Date | null
  droppedAt: Date | null
  lotteryRank: number | null
  createdAt: Date
  updatedAt: Date
  courseName: string | null
  courseCapacity: number | null
  courseEnrolledCount: number | null
  courseStatus: (typeof electiveCourses.status.enumValues)[number] | null
  dropReason: string | null
}

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

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

const mapSelectionRow = (
  r: SelectionCoreRow,
  studentNames: Map<string, string | null>
): CourseSelectionWithDetails => ({
  id: r.id,
  courseId: r.courseId,
  studentId: r.studentId,
  status: r.status,
  priority: r.priority,
  selectedAt: toIsoRequired(r.selectedAt),
  enrolledAt: toIso(r.enrolledAt),
  droppedAt: toIso(r.droppedAt),
  dropReason: r.dropReason,
  lotteryRank: r.lotteryRank,
  createdAt: toIsoRequired(r.createdAt),
  updatedAt: toIsoRequired(r.updatedAt),
  courseName: r.courseName,
  studentName: r.studentId ? (studentNames.get(r.studentId) ?? null) : null,
  courseCapacity: r.courseCapacity,
  courseEnrolledCount: r.courseEnrolledCount,
  courseStatus: r.courseStatus,
})

const buildSelectionCoreSelect = () =>
  db
    .select({
      id: courseSelections.id,
      courseId: courseSelections.courseId,
      studentId: courseSelections.studentId,
      status: courseSelections.status,
      priority: courseSelections.priority,
      selectedAt: courseSelections.selectedAt,
      enrolledAt: courseSelections.enrolledAt,
      droppedAt: courseSelections.droppedAt,
      dropReason: courseSelections.dropReason,
      lotteryRank: courseSelections.lotteryRank,
      createdAt: courseSelections.createdAt,
      updatedAt: courseSelections.updatedAt,
      courseName: electiveCourses.name,
      courseCapacity: electiveCourses.capacity,
      courseEnrolledCount: electiveCourses.enrolledCount,
      courseStatus: electiveCourses.status,
    })
    .from(courseSelections)
    .leftJoin(electiveCourses, eq(electiveCourses.id, courseSelections.courseId))

const resolveStudentDisplayNames = async (rows: SelectionCoreRow[]): Promise<Map<string, string | null>> => {
  const studentIds = Array.from(new Set(rows.map((r) => r.studentId).filter((v): v is string => typeof v === "string" && v.length > 0)))
  const userMap = await getCourseDisplayResolver().getUserNamesByIds(studentIds)
  const studentNames = new Map<string, string | null>()
  for (const [id, user] of userMap.entries()) {
    studentNames.set(id, user.name)
  }
  return studentNames
}

export const getCourseSelections = cache(
  async (
    courseId: string
  ): Promise<CourseSelectionWithDetails[]> => {
    const rows = await buildSelectionCoreSelect()
      .where(eq(courseSelections.courseId, courseId))
      .orderBy(asc(courseSelections.priority), asc(courseSelections.selectedAt))
    const studentNames = await resolveStudentDisplayNames(rows)
    return rows.map((r) => mapSelectionRow(r, studentNames))
  }
)

export const getStudentSelections = cache(
  async (
    studentId: string
  ): Promise<CourseSelectionWithDetails[]> => {
    const rows = await buildSelectionCoreSelect()
      .where(eq(courseSelections.studentId, studentId))
      .orderBy(desc(courseSelections.selectedAt))
    const studentNames = await resolveStudentDisplayNames(rows)
    return rows.map((r) => mapSelectionRow(r, studentNames))
  }
)

export const getStudentGradeId = cache(async (studentId: string): Promise<string | null> => {
  return getStudentGradeResolver().getStudentActiveGradeId(studentId)
})

export const getAvailableCoursesForStudent = cache(
  async (
    studentId: string,
    gradeId?: string | null
  ): Promise<ElectiveCourseWithDetails[]> => {
    const resolvedGradeId = gradeId ?? (await getStudentGradeId(studentId))
    const conditions: SQL[] = [eq(electiveCourses.status, "open")]
    if (resolvedGradeId) {
      conditions.push(
        sql`(${electiveCourses.gradeId} = ${resolvedGradeId} OR ${electiveCourses.gradeId} IS NULL)`
      )
    }
    const rows: CourseCoreRow[] = await buildCourseSelect()
      .where(and(...conditions))
      .orderBy(desc(electiveCourses.createdAt))
    const displayMaps = await resolveCourseDisplayNames(rows)
    return rows.map((r) => mapCourseRow(r, displayMaps.teacherNames, displayMaps.subjectNames, displayMaps.gradeNames))
  }
)
