import "server-only";

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

import { db } from "@/shared/db"
import {
  classes,
  classEnrollments,
  subjects,
  users,
} from "@/shared/db/schema"
import {
  getAssignmentIdsForStudents,
  getHomeworkSubmissionsForAssignments,
  getPublishedHomeworkAssignmentsWithSubject,
} from "@/modules/homework/data-access-classes"
import type {
  ClassStudent,
  StudentEnrolledClass,
} from "./types"
import {
  compareClassLike,
  getAccessibleClassIdsForTeacher,
  getSessionTeacherId,
  getTeacherSubjectIdsForClass,
} from "./data-access"

export const getStudentsSubjectScores = cache(
  async (studentIds: string[]): Promise<Map<string, Record<string, number | null>>> => {
    if (studentIds.length === 0) return new Map()

    // 1. Find assignments targeted at these students (via homework module data-access)
    const assignmentIds = await getAssignmentIdsForStudents(studentIds)
    if (assignmentIds.length === 0) return new Map()

    // 2. Get published assignment details including subject from linked exam (via homework module)
    const assignments = await getPublishedHomeworkAssignmentsWithSubject({ assignmentIds })

    // 3. Filter subjects (exclude PE, Music, Art)
    const excludeSubjects = ["体育", "音乐", "美术"]
    const subjectAssignments = new Map<string, string>() // subject -> assignmentId (latest)

    for (const a of assignments) {
      if (!a.subjectName) continue
      if (excludeSubjects.includes(a.subjectName)) continue
      if (!subjectAssignments.has(a.subjectName)) {
        subjectAssignments.set(a.subjectName, a.id)
      }
    }

    const targetAssignmentIds = Array.from(subjectAssignments.values())
    if (targetAssignmentIds.length === 0) return new Map()

    // 4. Get submissions for these assignments (via homework module)
    const submissions = await getHomeworkSubmissionsForAssignments(targetAssignmentIds)

    // 5. Map back to subject scores per student
    const studentScores = new Map<string, Record<string, number | null>>()

    // Create reverse map for assignment -> subject
    const assignmentSubjectMap = new Map<string, string>()
    for (const [subject, id] of subjectAssignments.entries()) {
      assignmentSubjectMap.set(id, subject)
    }

    for (const s of submissions) {
      const subject = assignmentSubjectMap.get(s.assignmentId)
      if (!subject) continue

      const existing = studentScores.get(s.studentId)
      const scores = existing ?? {}
      if (!existing) {
        studentScores.set(s.studentId, scores)
      }
      // Only set if not already set (since we ordered by desc createdAt, first one is latest)
      if (scores[subject] === undefined) {
        scores[subject] = s.score
      }
    }

    return studentScores
  }
)

export const getClassStudentSubjectScoresV2 = cache(
  async (params: { classId: string; teacherId?: string }): Promise<Map<string, Record<string, number | null>>> => {
    const teacherId = params.teacherId ?? (await getSessionTeacherId())
    if (!teacherId) return new Map()
    const classId = params.classId.trim()
    if (!classId) return new Map()

    const accessibleIds = await getAccessibleClassIdsForTeacher(teacherId)
    if (accessibleIds.length === 0 || !accessibleIds.includes(classId)) return new Map()

    const [classRow] = await db
      .select({ id: classes.id, teacherId: classes.teacherId })
      .from(classes)
      .where(eq(classes.id, classId))
      .limit(1)
    if (!classRow) return new Map()

    const isHomeroomTeacher = classRow.teacherId === teacherId
    const subjectIds = isHomeroomTeacher ? [] : await getTeacherSubjectIdsForClass(teacherId, classId)
    if (!isHomeroomTeacher && subjectIds.length === 0) return new Map()

    const enrollments = await db
      .select({ studentId: classEnrollments.studentId })
      .from(classEnrollments)
      .where(and(
        eq(classEnrollments.classId, classId),
        eq(classEnrollments.status, "active")
      ))

    const studentIds = enrollments.map((e) => e.studentId)
    const studentScores = await getStudentsSubjectScores(studentIds)
    if (subjectIds.length === 0) return studentScores

    // Map subjectIds to names for filtering
    const subjectRows = await db
      .select({ id: subjects.id, name: subjects.name })
      .from(subjects)
      .where(inArray(subjects.id, subjectIds))
    const allowed = new Set(subjectRows.map((s) => s.name))
    const filtered = new Map<string, Record<string, number | null>>()
    for (const [studentId, scores] of studentScores.entries()) {
      const nextScores: Record<string, number | null> = {}
      for (const [subject, score] of Object.entries(scores)) {
        if (allowed.has(subject)) nextScores[subject] = score
      }
      filtered.set(studentId, nextScores)
    }
    return filtered
  }
)

export const getStudentClasses = cache(async (studentId: string): Promise<StudentEnrolledClass[]> => {
  const id = studentId.trim()
  if (!id) return []

  const rows = await (async () => {
    try {
      return await db
        .select({
          id: classes.id,
          schoolName: classes.schoolName,
          name: classes.name,
          grade: classes.grade,
          homeroom: classes.homeroom,
          room: classes.room,
          teacherName: users.name,
          teacherEmail: users.email,
        })
        .from(classEnrollments)
        .innerJoin(classes, eq(classes.id, classEnrollments.classId))
        .leftJoin(users, eq(users.id, classes.teacherId))
        .where(and(eq(classEnrollments.studentId, id), eq(classEnrollments.status, "active")))
        .orderBy(asc(classes.schoolName), asc(classes.grade), asc(classes.name), asc(classes.homeroom), asc(classes.room))
    } catch (error) {
      console.error("getStudentClasses primary query failed, falling back:", error)
      return await db
        .select({
          id: classes.id,
          schoolName: sql<string | null>`NULL`.as("schoolName"),
          name: classes.name,
          grade: classes.grade,
          homeroom: classes.homeroom,
          room: classes.room,
          teacherName: users.name,
          teacherEmail: users.email,
        })
        .from(classEnrollments)
        .innerJoin(classes, eq(classes.id, classEnrollments.classId))
        .leftJoin(users, eq(users.id, classes.teacherId))
        .where(and(eq(classEnrollments.studentId, id), eq(classEnrollments.status, "active")))
        .orderBy(asc(classes.grade), asc(classes.name), asc(classes.homeroom), asc(classes.room))
    }
  })()

  const list = rows.map((r) => ({
    id: r.id,
    schoolName: r.schoolName,
    name: r.name,
    grade: r.grade,
    homeroom: r.homeroom,
    room: r.room,
    teacherName: r.teacherName,
    teacherEmail: r.teacherEmail,
  }))

  list.sort(compareClassLike)
  return list
})

/**
 * Get a single class details for a student (verifies enrollment).
 * Returns null if the student is not enrolled in the class.
 */
export const getStudentClassById = cache(
  async (studentId: string, classId: string): Promise<StudentEnrolledClass | null> => {
    const sid = studentId.trim()
    const cid = classId.trim()
    if (!sid || !cid) return null

    const rows = await db
      .select({
        id: classes.id,
        schoolName: classes.schoolName,
        name: classes.name,
        grade: classes.grade,
        homeroom: classes.homeroom,
        room: classes.room,
        teacherName: users.name,
        teacherEmail: users.email,
      })
      .from(classEnrollments)
      .innerJoin(classes, eq(classes.id, classEnrollments.classId))
      .leftJoin(users, eq(users.id, classes.teacherId))
      .where(and(eq(classEnrollments.studentId, sid), eq(classEnrollments.classId, cid), eq(classEnrollments.status, "active")))
      .limit(1)

    if (rows.length === 0) return null
    const r = rows[0]
    return {
      id: r.id,
      schoolName: r.schoolName,
      name: r.name,
      grade: r.grade,
      homeroom: r.homeroom,
      room: r.room,
      teacherName: r.teacherName,
      teacherEmail: r.teacherEmail,
    }
  }
)

export const getClassStudents = cache(
  async (params?: { classId?: string; q?: string; status?: string; teacherId?: string }): Promise<ClassStudent[]> => {
    const teacherId = params?.teacherId ?? (await getSessionTeacherId())
    if (!teacherId) return []

    const classId = params?.classId?.trim()
    const q = params?.q?.trim().toLowerCase()
    const status = params?.status?.trim().toLowerCase()

    const accessibleIds = await getAccessibleClassIdsForTeacher(teacherId)
    if (accessibleIds.length === 0) return []

    const conditions: SQL[] = [inArray(classes.id, accessibleIds)]

    if (classId) {
      conditions.push(eq(classes.id, classId))
    }

    if (status === "active" || status === "inactive") {
      conditions.push(eq(classEnrollments.status, status))
    }

    if (q && q.length > 0) {
      const needle = `%${q}%`
      conditions.push(
        sql`(LOWER(COALESCE(${users.name}, '')) LIKE ${needle} OR LOWER(${users.email}) LIKE ${needle})`
      )
    }

    const rows = await db
      .select({
        id: users.id,
        name: users.name,
        email: users.email,
        image: users.image,
        gender: users.gender,
        classId: classes.id,
        className: classes.name,
        status: classEnrollments.status,
        joinedAt: classEnrollments.createdAt,
      })
      .from(classEnrollments)
      .innerJoin(classes, eq(classes.id, classEnrollments.classId))
      .innerJoin(users, eq(users.id, classEnrollments.studentId))
      .where(and(...conditions))
      .orderBy(asc(users.name), asc(users.email))

    return rows.map((r) => ({
      id: r.id,
      name: r.name ?? "Unnamed",
      email: r.email,
      image: r.image,
      gender: r.gender,
      classId: r.classId,
      className: r.className,
      status: r.status,
      joinedAt: r.joinedAt,
    }))
  }
)

// ---------------------------------------------------------------------------
// DataScope resolver helpers (P1-5/P1-6 audit fix)
// These lightweight functions return only the IDs needed by the RBAC
// data-scope resolver, so shared/lib/auth-guard no longer queries classes/
// classEnrollments/classSubjectTeachers tables directly.
// ---------------------------------------------------------------------------

/**
 * Get a student's class IDs and grade IDs for DataScope resolution.
 * Joins classEnrollments with classes to resolve gradeId per enrollment.
 */
export async function getStudentScopeData(
  studentId: string,
): Promise<{ classIds: string[]; gradeIds: string[] }> {
  const rows = await db
    .select({ classId: classEnrollments.classId, gradeId: classes.gradeId })
    .from(classEnrollments)
    .innerJoin(classes, eq(classEnrollments.classId, classes.id))
    .where(eq(classEnrollments.studentId, studentId))

  const classIds = rows.map((r) => r.classId)
  const gradeIdSet = new Set<string>()
  for (const row of rows) {
    if (row.gradeId !== null && row.gradeId.trim().length > 0) {
      gradeIdSet.add(row.gradeId)
    }
  }

  return {
    classIds,
    gradeIds: Array.from(gradeIdSet),
  }
}

/**
 * Get grade IDs for a list of student IDs (used by parent DataScope).
 * Queries classEnrollments JOIN classes for all students in a single query.
 */
export async function getGradeIdsForStudentIds(
  studentIds: string[],
): Promise<string[]> {
  if (studentIds.length === 0) return []

  const rows = await db
    .select({ gradeId: classes.gradeId })
    .from(classEnrollments)
    .innerJoin(classes, eq(classEnrollments.classId, classes.id))
    .where(inArray(classEnrollments.studentId, studentIds))

  const gradeIdSet = new Set<string>()
  for (const row of rows) {
    if (row.gradeId !== null && row.gradeId.trim().length > 0) {
      gradeIdSet.add(row.gradeId)
    }
  }

  return Array.from(gradeIdSet)
}
