import "server-only";

import { cache } from "react"
import { and, asc, count, eq, inArray } from "drizzle-orm"

import { db } from "@/shared/db"
import {
  classes,
  classEnrollments,
  grades,
  schools,
} from "@/shared/db/schema"
import {
  getAssignmentIdsForStudents,
  getAssignmentMaxScoreById,
  getAssignmentTargetCounts,
  getHomeworkAssignmentsByIds,
  getHomeworkAssignmentsWithSubject,
  getHomeworkSubmissionsForStudents,
} from "@/modules/homework/data-access-classes"
import type {
  ClassHomeworkInsights,
  ClassHomeworkAssignmentStats,
  GradeHomeworkClassSummary,
  GradeHomeworkInsights,
  ScoreStats,
} from "./types"
import type { HomeworkSubmissionRecord } from "@/modules/homework/data-access-classes"
import {
  getAccessibleClassIdsForTeacher,
  getSessionTeacherId,
  getTeacherSubjectIdsForClass,
} from "./data-access"

const median = (sorted: number[]): number | null => {
  if (sorted.length === 0) return null
  const mid = Math.floor(sorted.length / 2)
  if (sorted.length % 2 === 1) return sorted[mid] ?? null
  const a = sorted[mid - 1]
  const b = sorted[mid]
  if (typeof a !== "number" || typeof b !== "number") return null
  return (a + b) / 2
}

const toScoreStats = (scores: number[]): ScoreStats => {
  if (scores.length === 0) return { count: 0, avg: null, median: null, min: null, max: null }
  const sorted = [...scores].sort((a, b) => a - b)
  const sum = sorted.reduce((acc, v) => acc + v, 0)
  return {
    count: sorted.length,
    avg: sum / sorted.length,
    median: median(sorted),
    min: sorted[0] ?? null,
    max: sorted[sorted.length - 1] ?? null,
  }
}

const buildLatestSubmissionByKey = (
  submissions: HomeworkSubmissionRecord[]
): Map<string, HomeworkSubmissionRecord> => {
  const map = new Map<string, HomeworkSubmissionRecord>()
  for (const s of submissions) {
    const key = `${s.assignmentId}:${s.studentId}`
    if (!map.has(key)) map.set(key, s)
  }
  return map
}

const computeAssignmentStats = (params: {
  assignments: Array<{
    id: string
    title: string
    status: string | null
    createdAt: Date
    dueAt: Date | null
    subjectName?: string | null
  }>
  studentIds: string[]
  latestByKey: Map<string, HomeworkSubmissionRecord>
  maxScoreByAssignmentId: Map<string, number>
  targetCountByAssignmentId: Map<string, number>
}): { stats: ClassHomeworkAssignmentStats[]; allScored: number[] } => {
  const { assignments, studentIds, latestByKey, maxScoreByAssignmentId, targetCountByAssignmentId } = params
  const allScored: number[] = []
  const nowMs = Date.now()

  const stats: ClassHomeworkAssignmentStats[] = assignments.map((a) => {
    const targetCount = targetCountByAssignmentId.get(a.id) ?? 0
    let submittedCount = 0
    let gradedCount = 0
    const scores: number[] = []
    const dueMs = a.dueAt ? a.dueAt.getTime() : null

    for (const studentId of studentIds) {
      const s = latestByKey.get(`${a.id}:${studentId}`)
      if (!s) continue

      const status = s.status ?? "started"
      if (status === "submitted" || status === "graded") submittedCount += 1
      if (status === "graded" || typeof s.score === "number") gradedCount += 1
      if (typeof s.score === "number") scores.push(s.score)
    }

    allScored.push(...scores)

    return {
      assignmentId: a.id,
      title: a.title,
      status: a.status ?? "draft",
      subject: a.subjectName ?? null,
      createdAt: a.createdAt.toISOString(),
      dueAt: a.dueAt ? a.dueAt.toISOString() : null,
      isActive: dueMs === null || dueMs >= nowMs,
      isOverdue: typeof dueMs === "number" && dueMs < nowMs,
      maxScore: maxScoreByAssignmentId.get(a.id) ?? 0,
      targetCount,
      submittedCount,
      gradedCount,
      scoreStats: toScoreStats(scores),
    }
  })

  return { stats, allScored }
}

export const getClassHomeworkInsights = cache(
  async (params: { classId: string; teacherId?: string; limit?: number }): Promise<ClassHomeworkInsights | null> => {
    const teacherId = params.teacherId ?? (await getSessionTeacherId())
    if (!teacherId) return null

    const classId = params.classId.trim()
    if (!classId) return null
    const accessibleIds = await getAccessibleClassIdsForTeacher(teacherId)
    if (accessibleIds.length === 0 || !accessibleIds.includes(classId)) return null

    const [classRow] = await db
      .select({
        id: classes.id,
        name: classes.name,
        grade: classes.grade,
        homeroom: classes.homeroom,
        room: classes.room,
        invitationCode: classes.invitationCode,
        teacherId: classes.teacherId,
      })
      .from(classes)
      .where(and(eq(classes.id, classId), inArray(classes.id, accessibleIds)))
      .limit(1)

    if (!classRow) return null
    const isHomeroomTeacher = classRow.teacherId === teacherId
    const subjectIdFilter = isHomeroomTeacher ? [] : await getTeacherSubjectIdsForClass(teacherId, classId)

    const enrollments = await db
      .select({
        studentId: classEnrollments.studentId,
        status: classEnrollments.status,
      })
      .from(classEnrollments)
      .innerJoin(classes, eq(classes.id, classEnrollments.classId))
      .where(and(inArray(classes.id, accessibleIds), eq(classEnrollments.classId, classId)))

    const activeStudentIds = enrollments.filter((e) => e.status === "active").map((e) => e.studentId)
    const inactiveStudentIds = enrollments.filter((e) => e.status !== "active").map((e) => e.studentId)
    const studentIds = enrollments.map((e) => e.studentId)

    if (!isHomeroomTeacher && subjectIdFilter.length === 0) {
      return {
        class: {
          id: classRow.id,
          name: classRow.name,
          grade: classRow.grade,
          homeroom: classRow.homeroom,
          room: classRow.room,
          invitationCode: classRow.invitationCode ?? null,
        },
        studentCounts: { total: studentIds.length, active: activeStudentIds.length, inactive: inactiveStudentIds.length },
        assignments: [],
        latest: null,
        overallScores: { count: 0, avg: null, median: null, min: null, max: null },
      }
    }

    if (studentIds.length === 0) {
      return {
        class: {
          id: classRow.id,
          name: classRow.name,
          grade: classRow.grade,
          homeroom: classRow.homeroom,
          room: classRow.room,
          invitationCode: classRow.invitationCode ?? null,
        },
        studentCounts: { total: 0, active: 0, inactive: 0 },
        assignments: [],
        latest: null,
        overallScores: { count: 0, avg: null, median: null, min: null, max: null },
      }
    }

    const assignmentIds = await getAssignmentIdsForStudents(studentIds)
    if (assignmentIds.length === 0) {
      return {
        class: {
          id: classRow.id,
          name: classRow.name,
          grade: classRow.grade,
          homeroom: classRow.homeroom,
          room: classRow.room,
          invitationCode: classRow.invitationCode ?? null,
        },
        studentCounts: { total: studentIds.length, active: activeStudentIds.length, inactive: inactiveStudentIds.length },
        assignments: [],
        latest: null,
        overallScores: { count: 0, avg: null, median: null, min: null, max: null },
      }
    }

    const limit = typeof params.limit === "number" && params.limit > 0 ? params.limit : 50
    const assignments = await getHomeworkAssignmentsWithSubject({
      assignmentIds,
      subjectIdFilter: subjectIdFilter.length > 0 ? subjectIdFilter : undefined,
      limit,
    })

    const usedAssignmentIds = assignments.map((a) => a.id)
    if (usedAssignmentIds.length === 0) {
      return {
        class: {
          id: classRow.id,
          name: classRow.name,
          grade: classRow.grade,
          homeroom: classRow.homeroom,
          room: classRow.room,
          invitationCode: classRow.invitationCode ?? null,
        },
        studentCounts: { total: studentIds.length, active: activeStudentIds.length, inactive: inactiveStudentIds.length },
        assignments: [],
        latest: null,
        overallScores: { count: 0, avg: null, median: null, min: null, max: null },
      }
    }

    const [maxScoreByAssignmentId, targetCountByAssignmentId, submissions] = await Promise.all([
      getAssignmentMaxScoreById(usedAssignmentIds),
      getAssignmentTargetCounts({ assignmentIds: usedAssignmentIds, studentIds }),
      getHomeworkSubmissionsForStudents({ assignmentIds: usedAssignmentIds, studentIds }),
    ])

    const latestByKey = buildLatestSubmissionByKey(submissions)
    const { stats, allScored } = computeAssignmentStats({
      assignments,
      studentIds,
      latestByKey,
      maxScoreByAssignmentId,
      targetCountByAssignmentId,
    })

    const overallScores = toScoreStats(allScored)
    const latest = stats[0] ?? null

    return {
      class: {
        id: classRow.id,
        name: classRow.name,
        grade: classRow.grade,
        homeroom: classRow.homeroom,
        room: classRow.room,
        invitationCode: classRow.invitationCode ?? null,
      },
      studentCounts: { total: studentIds.length, active: activeStudentIds.length, inactive: inactiveStudentIds.length },
      assignments: stats,
      latest,
      overallScores,
    }
  }
)

const avg = (values: number[]): number | null => {
  if (values.length === 0) return null
  const sum = values.reduce((acc, v) => acc + v, 0)
  return sum / values.length
}

export const getGradeHomeworkInsights = cache(
  async (params: { gradeId: string; limit?: number }): Promise<GradeHomeworkInsights | null> => {
    const gradeId = params.gradeId.trim()
    if (!gradeId) return null

    const [gradeRow] = await db
      .select({
        id: grades.id,
        name: grades.name,
        schoolId: schools.id,
        schoolName: schools.name,
      })
      .from(grades)
      .innerJoin(schools, eq(schools.id, grades.schoolId))
      .where(eq(grades.id, gradeId))
      .limit(1)

    if (!gradeRow) return null

    const classRows = await db
      .select({
        id: classes.id,
        name: classes.name,
        grade: classes.grade,
        homeroom: classes.homeroom,
        room: classes.room,
      })
      .from(classes)
      .where(eq(classes.gradeId, gradeId))
      .orderBy(asc(classes.name), asc(classes.homeroom), asc(classes.room))

    const classIds = classRows.map((r) => r.id)
    if (classIds.length === 0) {
      return {
        grade: { id: gradeRow.id, name: gradeRow.name, school: { id: gradeRow.schoolId, name: gradeRow.schoolName } },
        classCount: 0,
        studentCounts: { total: 0, active: 0, inactive: 0 },
        assignments: [],
        latest: null,
        overallScores: { count: 0, avg: null, median: null, min: null, max: null },
        classes: [],
      }
    }

    const enrollmentRows = await db
      .select({
        classId: classEnrollments.classId,
        studentId: classEnrollments.studentId,
        status: classEnrollments.status,
      })
      .from(classEnrollments)
      .where(inArray(classEnrollments.classId, classIds))

    const studentActiveById = new Map<string, boolean>()
    const studentsByClassId = new Map<string, { all: Set<string>; active: Set<string> }>()

    for (const e of enrollmentRows) {
      const prev = studentActiveById.get(e.studentId) ?? false
      const next = prev || e.status === "active"
      studentActiveById.set(e.studentId, next)

      const bucket = studentsByClassId.get(e.classId) ?? { all: new Set<string>(), active: new Set<string>() }
      bucket.all.add(e.studentId)
      if (e.status === "active") bucket.active.add(e.studentId)
      studentsByClassId.set(e.classId, bucket)
    }

    const studentIds = Array.from(studentActiveById.keys())
    const activeCount = Array.from(studentActiveById.values()).filter(Boolean).length
    const inactiveCount = studentIds.length - activeCount

    if (studentIds.length === 0) {
      const summaries: GradeHomeworkClassSummary[] = classRows.map((c) => ({
        class: { id: c.id, name: c.name, grade: c.grade, homeroom: c.homeroom, room: c.room },
        studentCounts: { total: 0, active: 0, inactive: 0 },
        latestAvg: null,
        prevAvg: null,
        deltaAvg: null,
        overallScores: { count: 0, avg: null, median: null, min: null, max: null },
      }))

      return {
        grade: { id: gradeRow.id, name: gradeRow.name, school: { id: gradeRow.schoolId, name: gradeRow.schoolName } },
        classCount: classRows.length,
        studentCounts: { total: 0, active: 0, inactive: 0 },
        assignments: [],
        latest: null,
        overallScores: { count: 0, avg: null, median: null, min: null, max: null },
        classes: summaries,
      }
    }

    const assignmentIds = await getAssignmentIdsForStudents(studentIds)
    if (assignmentIds.length === 0) {
      const summaries: GradeHomeworkClassSummary[] = classRows.map((c) => {
        const bucket = studentsByClassId.get(c.id) ?? { all: new Set<string>(), active: new Set<string>() }
        return {
          class: { id: c.id, name: c.name, grade: c.grade, homeroom: c.homeroom, room: c.room },
          studentCounts: { total: bucket.all.size, active: bucket.active.size, inactive: bucket.all.size - bucket.active.size },
          latestAvg: null,
          prevAvg: null,
          deltaAvg: null,
          overallScores: { count: 0, avg: null, median: null, min: null, max: null },
        }
      })

      return {
        grade: { id: gradeRow.id, name: gradeRow.name, school: { id: gradeRow.schoolId, name: gradeRow.schoolName } },
        classCount: classRows.length,
        studentCounts: { total: studentIds.length, active: activeCount, inactive: inactiveCount },
        assignments: [],
        latest: null,
        overallScores: { count: 0, avg: null, median: null, min: null, max: null },
        classes: summaries,
      }
    }

    const limit = typeof params.limit === "number" && params.limit > 0 ? params.limit : 50
    const assignments = await getHomeworkAssignmentsByIds({ assignmentIds, limit })

    const usedAssignmentIds = assignments.map((a) => a.id)
    if (usedAssignmentIds.length === 0) {
      const summaries: GradeHomeworkClassSummary[] = classRows.map((c) => {
        const bucket = studentsByClassId.get(c.id) ?? { all: new Set<string>(), active: new Set<string>() }
        return {
          class: { id: c.id, name: c.name, grade: c.grade, homeroom: c.homeroom, room: c.room },
          studentCounts: { total: bucket.all.size, active: bucket.active.size, inactive: bucket.all.size - bucket.active.size },
          latestAvg: null,
          prevAvg: null,
          deltaAvg: null,
          overallScores: { count: 0, avg: null, median: null, min: null, max: null },
        }
      })

      return {
        grade: { id: gradeRow.id, name: gradeRow.name, school: { id: gradeRow.schoolId, name: gradeRow.schoolName } },
        classCount: classRows.length,
        studentCounts: { total: studentIds.length, active: activeCount, inactive: inactiveCount },
        assignments: [],
        latest: null,
        overallScores: { count: 0, avg: null, median: null, min: null, max: null },
        classes: summaries,
      }
    }

    const [maxScoreByAssignmentId, targetCountByAssignmentId, submissions] = await Promise.all([
      getAssignmentMaxScoreById(usedAssignmentIds),
      getAssignmentTargetCounts({ assignmentIds: usedAssignmentIds, studentIds }),
      getHomeworkSubmissionsForStudents({ assignmentIds: usedAssignmentIds, studentIds }),
    ])

    const latestByKey = buildLatestSubmissionByKey(submissions)
    const { stats, allScored } = computeAssignmentStats({
      assignments,
      studentIds,
      latestByKey,
      maxScoreByAssignmentId,
      targetCountByAssignmentId,
    })

    const overallScores = toScoreStats(allScored)
    const latest = stats[0] ?? null
    const latestAssignmentId = stats[0]?.assignmentId ?? null
    const prevAssignmentId = stats[1]?.assignmentId ?? null

    const classSummaries: GradeHomeworkClassSummary[] = classRows.map((c) => {
      const bucket = studentsByClassId.get(c.id) ?? { all: new Set<string>(), active: new Set<string>() }
      const classStudentIds = Array.from(bucket.all)

      const latestScores: number[] = []
      const prevScores: number[] = []
      const overallClassScores: number[] = []

      if (latestAssignmentId) {
        for (const studentId of classStudentIds) {
          const s = latestByKey.get(`${latestAssignmentId}:${studentId}`)
          if (typeof s?.score === "number") latestScores.push(s.score)
        }
      }

      if (prevAssignmentId) {
        for (const studentId of classStudentIds) {
          const s = latestByKey.get(`${prevAssignmentId}:${studentId}`)
          if (typeof s?.score === "number") prevScores.push(s.score)
        }
      }

      for (const assignmentId of usedAssignmentIds) {
        for (const studentId of classStudentIds) {
          const s = latestByKey.get(`${assignmentId}:${studentId}`)
          if (typeof s?.score === "number") overallClassScores.push(s.score)
        }
      }

      const latestAvg = avg(latestScores)
      const prevAvg = avg(prevScores)

      return {
        class: { id: c.id, name: c.name, grade: c.grade, homeroom: c.homeroom, room: c.room },
        studentCounts: { total: bucket.all.size, active: bucket.active.size, inactive: bucket.all.size - bucket.active.size },
        latestAvg,
        prevAvg,
        deltaAvg: typeof latestAvg === "number" && typeof prevAvg === "number" ? latestAvg - prevAvg : null,
        overallScores: toScoreStats(overallClassScores),
      }
    })

    classSummaries.sort((a, b) => (b.latestAvg ?? -Infinity) - (a.latestAvg ?? -Infinity))

    return {
      grade: { id: gradeRow.id, name: gradeRow.name, school: { id: gradeRow.schoolId, name: gradeRow.schoolName } },
      classCount: classRows.length,
      studentCounts: { total: studentIds.length, active: activeCount, inactive: inactiveCount },
      assignments: stats,
      latest,
      overallScores,
      classes: classSummaries,
    }
  }
)

export type ClassesDashboardStats = {
  classCount: number
}

export const getClassesDashboardStats = cache(async (): Promise<ClassesDashboardStats> => {
  const [row] = await db.select({ value: count() }).from(classes)
  return { classCount: Number(row?.value ?? 0) }
})
