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 {
  classes,
  coursePlanItems,
  coursePlans,
  subjects,
  users,
} from "@/shared/db/schema"
import type {
  CoursePlan,
  CoursePlanItem,
  CoursePlanListItem,
  CoursePlanStatus,
  CoursePlanWithItems,
  GetCoursePlansParams,
  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()

const mapPlanRow = (
  row: {
    id: string
    classId: string
    subjectId: string
    teacherId: string
    academicYearId: string | null
    semester: "1" | "2"
    totalHours: number
    completedHours: number
    weeklyHours: number
    startDate: Date | null
    endDate: Date | null
    syllabus: string | null
    objectives: string | null
    status: CoursePlanStatus
    createdBy: string
    createdAt: Date
    updatedAt: Date
    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: row.className,
  subjectName: row.subjectName,
  teacherName: row.teacherName,
})

const mapItemRow = (
  row: {
    id: string
    planId: string
    week: number
    topic: string
    content: string | null
    hours: number
    textbookChapter: string | null
    notes: string | null
    isCompleted: boolean
    completedAt: Date | null
    createdAt: Date
    updatedAt: Date
  }
): 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),
})

const buildPlanSelect = () =>
  db
    .select({
      id: coursePlans.id,
      classId: coursePlans.classId,
      subjectId: coursePlans.subjectId,
      teacherId: coursePlans.teacherId,
      academicYearId: coursePlans.academicYearId,
      semester: coursePlans.semester,
      totalHours: coursePlans.totalHours,
      completedHours: coursePlans.completedHours,
      weeklyHours: coursePlans.weeklyHours,
      startDate: coursePlans.startDate,
      endDate: coursePlans.endDate,
      syllabus: coursePlans.syllabus,
      objectives: coursePlans.objectives,
      status: coursePlans.status,
      createdBy: coursePlans.createdBy,
      createdAt: coursePlans.createdAt,
      updatedAt: coursePlans.updatedAt,
      className: classes.name,
      subjectName: subjects.name,
      teacherName: users.name,
    })
    .from(coursePlans)
    .leftJoin(classes, eq(classes.id, coursePlans.classId))
    .leftJoin(subjects, eq(subjects.id, coursePlans.subjectId))
    .leftJoin(users, eq(users.id, coursePlans.teacherId))

export const getCoursePlans = cache(
  async (params?: GetCoursePlansParams): Promise<CoursePlanListItem[]> => {
    try {
      const conditions: SQL[] = []
      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 = buildPlanSelect()
      const rows = await (conditions.length > 0
        ? query.where(and(...conditions))
        : query
      ).orderBy(desc(coursePlans.createdAt))

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

export const getCoursePlanById = cache(
  async (id: string): Promise<CoursePlanWithItems | null> => {
    try {
      const [planRow] = await buildPlanSelect()
        .where(eq(coursePlans.id, id))
        .limit(1)

      if (!planRow) return null

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

      return {
        ...mapPlanRow(planRow),
        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 ? new Date(data.startDate) : null,
    endDate: data.endDate ? new Date(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 ? new Date(data.startDate) : null
  if (data.endDate !== undefined)
    update.endDate = data.endDate ? new Date(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 ? new Date(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))
    )
  )
}

export type { CoursePlan, CoursePlanItem, CoursePlanWithItems }

export const getSubjectOptions = cache(async (): Promise<{ id: string; name: string }[]> => {
  try {
    const rows = await db
      .select({ id: subjects.id, name: subjects.name })
      .from(subjects)
      .orderBy(asc(subjects.order), asc(subjects.name))
    return rows.map((r) => ({ id: r.id, name: r.name }))
  } catch (error) {
    console.error("getSubjectOptions failed:", error)
    return []
  }
})
