import "server-only"

import { eq } from "drizzle-orm"

import { db } from "@/shared/db"
import { classSchedule } from "@/shared/db/schema"
import {
  getTeacherIdForMutations,
  verifyTeacherOwnsClass,
} from "@/modules/classes/data-access"
import {
  insertClassScheduleItem,
  updateClassScheduleItemById,
  deleteClassScheduleItemById,
} from "./data-access"
import type {
  CreateClassScheduleItemInput,
  UpdateClassScheduleItemInput,
} from "./types"

const isTimeHHMM = (v: string): boolean => /^\d{2}:\d{2}$/.test(v)

/**
 * Create a single classSchedule item.
 * Ownership: the caller (teacher) must own the target class.
 * DB write is delegated to the unified scheduling write entry point.
 */
export async function createClassScheduleItem(
  data: CreateClassScheduleItemInput,
): Promise<string> {
  const teacherId = await getTeacherIdForMutations()

  const classId = data.classId.trim()
  const course = data.course.trim()
  const startTime = data.startTime.trim()
  const endTime = data.endTime.trim()
  const location = data.location?.trim() || null
  const weekday = data.weekday

  if (!classId) throw new Error("Class is required")
  if (!course) throw new Error("Course is required")
  if (!isTimeHHMM(startTime) || !isTimeHHMM(endTime)) throw new Error("Invalid time format")
  if (startTime >= endTime) throw new Error("Start time must be earlier than end time")
  if (weekday < 1 || weekday > 7) throw new Error("Invalid weekday")

  const owned = await verifyTeacherOwnsClass(classId, teacherId)
  if (!owned) throw new Error("Class not found")

  return insertClassScheduleItem({
    classId,
    weekday,
    startTime,
    endTime,
    course,
    location,
  })
}

/**
 * Update a classSchedule item by id.
 * Ownership: the teacher must own the class associated with the schedule item
 * (and the target class when classId is being changed).
 */
export async function updateClassScheduleItem(
  scheduleId: string,
  data: UpdateClassScheduleItemInput,
): Promise<void> {
  const teacherId = await getTeacherIdForMutations()
  const id = scheduleId.trim()
  if (!id) throw new Error("Missing schedule id")

  const [existing] = await db
    .select({
      id: classSchedule.id,
      classId: classSchedule.classId,
      startTime: classSchedule.startTime,
      endTime: classSchedule.endTime,
    })
    .from(classSchedule)
    .where(eq(classSchedule.id, id))
    .limit(1)

  if (!existing) throw new Error("Schedule item not found")

  const ownedExisting = await verifyTeacherOwnsClass(existing.classId, teacherId)
  if (!ownedExisting) throw new Error("Schedule item not found")

  const update: Partial<typeof classSchedule.$inferSelect> = {}

  if (typeof data.classId === "string") {
    const nextClassId = data.classId.trim()
    if (!nextClassId) throw new Error("Class is required")

    const ownedNext = await verifyTeacherOwnsClass(nextClassId, teacherId)
    if (!ownedNext) throw new Error("Class not found")
    update.classId = nextClassId
  }

  if (typeof data.weekday === "number") {
    if (data.weekday < 1 || data.weekday > 7) throw new Error("Invalid weekday")
    update.weekday = data.weekday
  }

  if (typeof data.course === "string") {
    const course = data.course.trim()
    if (!course) throw new Error("Course is required")
    update.course = course
  }

  const nextStart = typeof data.startTime === "string" ? data.startTime.trim() : undefined
  const nextEnd = typeof data.endTime === "string" ? data.endTime.trim() : undefined
  if (nextStart !== undefined) {
    if (!isTimeHHMM(nextStart)) throw new Error("Invalid time format")
    update.startTime = nextStart
  }
  if (nextEnd !== undefined) {
    if (!isTimeHHMM(nextEnd)) throw new Error("Invalid time format")
    update.endTime = nextEnd
  }

  if (update.startTime !== undefined || update.endTime !== undefined) {
    const mergedStart = update.startTime ?? existing.startTime
    const mergedEnd = update.endTime ?? existing.endTime
    if (typeof mergedStart === "string" && typeof mergedEnd === "string" && mergedStart >= mergedEnd) {
      throw new Error("Start time must be earlier than end time")
    }
  }

  if (data.location !== undefined) {
    update.location = data.location?.trim() || null
  }

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

  await updateClassScheduleItemById(id, update)
}

/**
 * Delete a classSchedule item by id.
 * Ownership: the teacher must own the class associated with the schedule item.
 */
export async function deleteClassScheduleItem(scheduleId: string): Promise<void> {
  const teacherId = await getTeacherIdForMutations()
  const id = scheduleId.trim()
  if (!id) throw new Error("Missing schedule id")

  const [existing] = await db
    .select({ id: classSchedule.id, classId: classSchedule.classId })
    .from(classSchedule)
    .where(eq(classSchedule.id, id))
    .limit(1)

  if (!existing) throw new Error("Schedule item not found")

  const owned = await verifyTeacherOwnsClass(existing.classId, teacherId)
  if (!owned) throw new Error("Schedule item not found")

  await deleteClassScheduleItemById(id)
}
