import "server-only"

import { cache } from "react"
import { createId } from "@paralleldrive/cuid2"
import { and, count, desc, eq, inArray, sql } from "drizzle-orm"

import { db } from "@/shared/db"
import { gradeRecords } from "@/shared/db/schema"
import {
  getActiveStudentIdsByClassId,
  getClassExists,
  getClassNameById,
  getClassNamesByIds,
  getStudentActiveClassId,
} from "@/modules/classes/data-access"
import { getSubjectOptions } from "@/modules/school/data-access"
import { getUserNamesByIds } from "@/modules/users/data-access"
import { NotFoundError } from "@/shared/lib/action-utils"
import type { DataScope } from "@/shared/types/permissions"

import { toNumber } from "./lib/grade-utils"
import { buildScopeClassFilter } from "./lib/scope-filter"
import { computeAverageScore, computeGradeStats } from "./stats-service"
import type {
  ClassGradeStats,
  ClassRankingItem,
  GradeQueryParams,
  GradeRecord,
  GradeRecordListItem,
  GradeStats,
  StudentGradeSummary,
} from "./types"
import type {
  BatchCreateGradeRecordInput,
  CreateGradeRecordInput,
  UpdateGradeRecordInput,
} from "./schema"

/**
 * grades 模块核心数据访问层。
 *
 * P1-2 重构：
 * - 草稿 CRUD（saveGradeDraft/getGradeDraft/deleteGradeDraft）已迁移至 data-access-drafts.ts
 * - 按试卷录入（batchCreateGradeRecordsByExam）已迁移至 data-access-exam-entry.ts
 * 本文件仅保留成绩记录的核心 CRUD 与统计查询。
 */

/** 分页查询结果 */
export interface PaginatedGradeRecords {
  records: GradeRecordListItem[]
  total: number
}

const serializeRecord = (r: typeof gradeRecords.$inferSelect): GradeRecord => ({
  id: r.id,
  studentId: r.studentId,
  classId: r.classId,
  subjectId: r.subjectId,
  examId: r.examId ?? null,
  academicYearId: r.academicYearId ?? null,
  title: r.title,
  score: String(r.score),
  fullScore: String(r.fullScore),
  type: r.type,
  semester: r.semester,
  recordedBy: r.recordedBy,
  remark: r.remark ?? null,
  createdAt: r.createdAt.toISOString(),
  updatedAt: r.updatedAt.toISOString(),
})

export const getGradeRecords = cache(
  async (
    params: GradeQueryParams & {
      scope: DataScope
      currentUserId?: string
      limit?: number
      offset?: number
    }
  ): Promise<PaginatedGradeRecords> => {
    const conditions = []

    const scopeFilter = buildScopeClassFilter(params.scope, params.currentUserId)
    if (scopeFilter) conditions.push(scopeFilter)

    if (params.classId) conditions.push(eq(gradeRecords.classId, params.classId))
    if (params.subjectId) conditions.push(eq(gradeRecords.subjectId, params.subjectId))
    if (params.studentId) conditions.push(eq(gradeRecords.studentId, params.studentId))
    if (params.type) conditions.push(eq(gradeRecords.type, params.type))
    if (params.semester) conditions.push(eq(gradeRecords.semester, params.semester))
    if (params.examId) conditions.push(eq(gradeRecords.examId, params.examId))

    const whereClause = conditions.length > 0 ? and(...conditions) : undefined

    // 并行查询总数与当前页数据
    const [countRow, rows] = await Promise.all([
      db.select({ total: count() }).from(gradeRecords).where(whereClause),
      db
        .select({
          record: gradeRecords,
        })
        .from(gradeRecords)
        .where(whereClause)
        .orderBy(desc(gradeRecords.createdAt))
        .limit(params.limit ?? 1000)
        .offset(params.offset ?? 0),
    ])

    const total = toNumber(countRow[0]?.total)
    if (rows.length === 0) return { records: [], total }

    // Batch fetch display names via cross-module interfaces
    const studentIds = Array.from(new Set(rows.map((r) => r.record.studentId)))
    const classIds = Array.from(new Set(rows.map((r) => r.record.classId).filter((v): v is string => typeof v === "string" && v.length > 0)))
    const recorderIds = Array.from(new Set(rows.map((r) => r.record.recordedBy)))

    const [studentNameMap, classNameMap, subjectOptions, recorderNameMap] = await Promise.all([
      getUserNamesByIds(studentIds),
      getClassNamesByIds(classIds),
      getSubjectOptions(),
      getUserNamesByIds(recorderIds),
    ])

    const subjectNameById = new Map<string, string>()
    for (const s of subjectOptions) subjectNameById.set(s.id, s.name)

    const records: GradeRecordListItem[] = rows.map((r) => ({
      id: r.record.id,
      studentId: r.record.studentId,
      studentName: studentNameMap.get(r.record.studentId)?.name ?? "Unknown",
      classId: r.record.classId,
      className: r.record.classId ? classNameMap.get(r.record.classId) ?? "Unknown" : "Unknown",
      subjectId: r.record.subjectId,
      subjectName: r.record.subjectId ? subjectNameById.get(r.record.subjectId) ?? "Unknown" : "Unknown",
      examId: r.record.examId ?? null,
      title: r.record.title,
      score: toNumber(r.record.score),
      fullScore: toNumber(r.record.fullScore),
      type: r.record.type,
      semester: r.record.semester,
      recordedBy: r.record.recordedBy,
      recorderName: recorderNameMap.get(r.record.recordedBy)?.name ?? "Unknown",
      remark: r.record.remark ?? null,
      createdAt: r.record.createdAt.toISOString(),
    }))

    return { records, total }
  }
)

export const getGradeRecordById = cache(async (id: string): Promise<GradeRecord | null> => {
  const [row] = await db.select().from(gradeRecords).where(eq(gradeRecords.id, id)).limit(1)
  return row ? serializeRecord(row) : null
})

export async function createGradeRecord(
  data: CreateGradeRecordInput,
  recordedBy: string
): Promise<string> {
  const id = createId()
  await db.insert(gradeRecords).values({
    id,
    studentId: data.studentId,
    classId: data.classId,
    subjectId: data.subjectId,
    examId: data.examId ?? null,
    academicYearId: data.academicYearId ?? null,
    title: data.title,
    score: String(data.score),
    fullScore: String(data.fullScore ?? 100),
    type: data.type ?? "exam",
    semester: data.semester ?? "1",
    recordedBy,
    remark: data.remark ?? null,
  })
  return id
}

export async function batchCreateGradeRecords(
  data: BatchCreateGradeRecordInput,
  recordedBy: string
): Promise<string[]> {
  const rows = data.records.map((r) => ({
    id: createId(),
    studentId: r.studentId,
    classId: data.classId,
    subjectId: data.subjectId,
    examId: data.examId ?? null,
    academicYearId: data.academicYearId ?? null,
    title: data.title,
    score: String(r.score),
    fullScore: String(data.fullScore ?? 100),
    type: data.type ?? "exam",
    semester: data.semester ?? "1",
    recordedBy,
    remark: r.remark ?? null,
  }))

  if (rows.length === 0) return []
  // P3 修复：批量插入使用事务，确保原子性
  await db.transaction(async (tx) => {
    await tx.insert(gradeRecords).values(rows)
  })
  // v3-P2-3：返回创建的记录 ID 列表，供前端撤销使用
  return rows.map((r) => r.id)
}

/**
 * v3-P2-3: 批量撤销成绩录入。
 * 仅允许撤销最近一次批量录入的记录（通过 ID 列表），且仅允许撤销自己录入的记录。
 */
export async function undoBatchCreateGradeRecords(
  ids: string[],
  recordedBy: string
): Promise<number> {
  if (ids.length === 0) return 0
  // 仅允许撤销自己录入的记录，避免越权删除
  const rows = await db
    .select({ id: gradeRecords.id })
    .from(gradeRecords)
    .where(
      and(
        inArray(gradeRecords.id, ids),
        eq(gradeRecords.recordedBy, recordedBy)
      )
    )
  if (rows.length === 0) return 0
  const allowedIds = rows.map((r) => r.id)
  await db.delete(gradeRecords).where(inArray(gradeRecords.id, allowedIds))
  return allowedIds.length
}

export async function updateGradeRecord(
  id: string,
  data: UpdateGradeRecordInput
): Promise<void> {
  // P3 修复：检查记录是否存在
  const [existing] = await db
    .select({ id: gradeRecords.id })
    .from(gradeRecords)
    .where(eq(gradeRecords.id, id))
    .limit(1)
  if (!existing) {
    throw new NotFoundError("成绩记录")
  }

  // P2-3 修复：使用 Partial<typeof gradeRecords.$inferInsert> 替代 Record<string, unknown>
  const update: Partial<typeof gradeRecords.$inferInsert> = { updatedAt: new Date() }
  if (data.title !== undefined) update.title = data.title
  if (data.score !== undefined) update.score = String(data.score)
  if (data.fullScore !== undefined) update.fullScore = String(data.fullScore)
  if (data.type !== undefined) update.type = data.type
  if (data.semester !== undefined) update.semester = data.semester
  if (data.remark !== undefined) update.remark = data.remark
  if (data.examId !== undefined) update.examId = data.examId

  await db.update(gradeRecords).set(update).where(eq(gradeRecords.id, id))
}

export async function deleteGradeRecord(id: string): Promise<void> {
  // P3 修复：检查记录是否存在
  const [existing] = await db
    .select({ id: gradeRecords.id })
    .from(gradeRecords)
    .where(eq(gradeRecords.id, id))
    .limit(1)
  if (!existing) {
    throw new NotFoundError("成绩记录")
  }

  await db.delete(gradeRecords).where(eq(gradeRecords.id, id))
}

/**
 * v3-P3-2: 批量删除成绩记录。
 * 使用 inArray 一次性删除，避免 N+1 查询。
 * 返回实际删除的记录数。
 */
export async function bulkDeleteGradeRecords(ids: string[]): Promise<number> {
  if (ids.length === 0) return 0
  // 先查询存在的记录，避免删除不存在的 ID 时影响计数
  const rows = await db
    .select({ id: gradeRecords.id })
    .from(gradeRecords)
    .where(inArray(gradeRecords.id, ids))
  if (rows.length === 0) return 0
  const existingIds = rows.map((r) => r.id)
  await db.delete(gradeRecords).where(inArray(gradeRecords.id, existingIds))
  return existingIds.length
}

export const getClassGradeStats = cache(
  async (
    classId: string,
    subjectId?: string,
    examId?: string,
    scope?: DataScope,
    currentUserId?: string
  ): Promise<GradeStats | null> => {
    const conditions = [eq(gradeRecords.classId, classId)]
    if (subjectId) conditions.push(eq(gradeRecords.subjectId, subjectId))
    if (examId) conditions.push(eq(gradeRecords.examId, examId))

    // P3 修复：添加 scope 过滤
    if (scope) {
      const scopeFilter = buildScopeClassFilter(scope, currentUserId)
      if (scopeFilter) conditions.push(scopeFilter)
    }

    const rows = await db
      .select({
        score: gradeRecords.score,
        fullScore: gradeRecords.fullScore,
      })
      .from(gradeRecords)
      .where(and(...conditions))

    return computeGradeStats(rows)
  }
)

/**
 * P2-4: 计算单个学生在班级中的排名。
 *
 * 解耦自 getClassRanking：避免获取整个班级排名（含姓名查询）只为查单个学生排名。
 * 使用 SQL 直接计算：rank = (平均分严格高于该学生的不同平均分数) + 1。
 * 处理并列：相同平均分的学生共享名次（competition ranking）。
 */
export async function getStudentRankInClass(
  studentId: string,
  classId: string,
  scope?: DataScope
): Promise<number> {
  const conditions = [eq(gradeRecords.classId, classId)]

  if (scope) {
    const scopeFilter = buildScopeClassFilter(scope, studentId)
    if (scopeFilter) conditions.push(scopeFilter)
  }

  // 查询该学生在此班级的平均分
  const [studentAvgRow] = await db
    .select({
      avgScore: sql<number>`AVG(${gradeRecords.score})`,
    })
    .from(gradeRecords)
    .where(and(...conditions, eq(gradeRecords.studentId, studentId)))

  if (!studentAvgRow || studentAvgRow.avgScore === null) return 0

  const studentAvg = toNumber(studentAvgRow.avgScore)

  // 获取所有学生的平均分，计算平均分严格高于该学生的不同平均分数（处理并列）
  const allStudentAvgs = await db
    .select({
      studentId: gradeRecords.studentId,
      avgScore: sql<number>`AVG(${gradeRecords.score})`,
    })
    .from(gradeRecords)
    .where(and(...conditions))
    .groupBy(gradeRecords.studentId)

  const distinctHigherAvgs = new Set<number>()
  for (const row of allStudentAvgs) {
    if (row.studentId !== studentId) {
      const otherAvg = toNumber(row.avgScore)
      if (otherAvg > studentAvg) {
        distinctHigherAvgs.add(Math.round(otherAvg * 100) / 100)
      }
    }
  }

  return distinctHigherAvgs.size + 1
}

export const getStudentGradeSummary = cache(
  async (
    studentId: string,
    scope?: DataScope
  ): Promise<StudentGradeSummary | null> => {
    // P3 修复：对 class_taught scope 校验学生是否属于教师所教的班级
    if (scope?.type === "class_taught") {
      const allowedClassIds = new Set(scope.classIds)
      const studentClassId = await getStudentActiveClassId(studentId)
      if (studentClassId && !allowedClassIds.has(studentClassId)) {
        return null
      }
    }

    const studentNameMap = await getUserNamesByIds([studentId])
    const studentName = studentNameMap.get(studentId)?.name ?? null
    if (!studentName && !studentNameMap.has(studentId)) return null

    const conditions = [eq(gradeRecords.studentId, studentId)]

    // 应用 scope 过滤
    if (scope) {
      const scopeFilter = buildScopeClassFilter(scope, studentId)
      if (scopeFilter) conditions.push(scopeFilter)
    }

    const records = await db
      .select({
        record: gradeRecords,
      })
      .from(gradeRecords)
      .where(and(...conditions))
      .orderBy(desc(gradeRecords.createdAt))

    if (records.length === 0) {
      return {
        studentId,
        studentName: studentName ?? "Unknown",
        records: [],
        averageScore: 0,
        rank: 0,
      }
    }

    // Batch fetch display names via cross-module interfaces
    const classIds = Array.from(new Set(records.map((r) => r.record.classId).filter((v): v is string => typeof v === "string" && v.length > 0)))
    const recorderIds = Array.from(new Set(records.map((r) => r.record.recordedBy)))

    const [classNameMap, subjectOptions, recorderNameMap] = await Promise.all([
      getClassNamesByIds(classIds),
      getSubjectOptions(),
      getUserNamesByIds(recorderIds),
    ])

    const subjectNameById = new Map<string, string>()
    for (const s of subjectOptions) subjectNameById.set(s.id, s.name)

    const listItems: GradeRecordListItem[] = records.map((r) => ({
      id: r.record.id,
      studentId: r.record.studentId,
      studentName: studentName ?? "Unknown",
      classId: r.record.classId,
      className: r.record.classId ? classNameMap.get(r.record.classId) ?? "Unknown" : "Unknown",
      subjectId: r.record.subjectId,
      subjectName: r.record.subjectId ? subjectNameById.get(r.record.subjectId) ?? "Unknown" : "Unknown",
      examId: r.record.examId ?? null,
      title: r.record.title,
      score: toNumber(r.record.score),
      fullScore: toNumber(r.record.fullScore),
      type: r.record.type,
      semester: r.record.semester,
      recordedBy: r.record.recordedBy,
      recorderName: recorderNameMap.get(r.record.recordedBy)?.name ?? "Unknown",
      remark: r.record.remark ?? null,
      createdAt: r.record.createdAt.toISOString(),
    }))

    const avg = computeAverageScore(listItems.map((i) => i.score))

    // P2-4 修复：使用独立的 getStudentRankInClass 函数替代 getClassRanking，
    // 避免获取整个班级排名只需查单个学生排名的 N+1 查询
    let rank = 0
    const studentClassId = await getStudentActiveClassId(studentId)
    if (studentClassId) {
      rank = await getStudentRankInClass(studentId, studentClassId, scope)
    }

    return {
      studentId,
      studentName: studentName ?? "Unknown",
      records: listItems,
      averageScore: avg,
      rank,
    }
  }
)

export const getClassRanking = cache(
  async (
    classId: string,
    subjectId?: string,
    examId?: string,
    scope?: DataScope,
    currentUserId?: string
  ): Promise<ClassRankingItem[]> => {
    const conditions = [eq(gradeRecords.classId, classId)]
    if (subjectId) conditions.push(eq(gradeRecords.subjectId, subjectId))
    if (examId) conditions.push(eq(gradeRecords.examId, examId))

    // P3 修复：添加 scope 过滤
    if (scope) {
      const scopeFilter = buildScopeClassFilter(scope, currentUserId)
      if (scopeFilter) conditions.push(scopeFilter)
    }

    const rows = await db
      .select({
        studentId: gradeRecords.studentId,
        avgScore: sql<number>`AVG(${gradeRecords.score})`,
        recordCount: count(gradeRecords.id),
      })
      .from(gradeRecords)
      .where(and(...conditions))
      .groupBy(gradeRecords.studentId)
      .orderBy(desc(sql`AVG(${gradeRecords.score})`))

    if (rows.length === 0) return []

    const studentIds = Array.from(new Set(rows.map((r) => r.studentId)))
    const studentNameMap = await getUserNamesByIds(studentIds)

    // P3 修复：处理并列排名（相同平均分获得相同名次）
    const ranked: ClassRankingItem[] = []
    let lastAvg: number | null = null
    let lastRank = 0
    for (let i = 0; i < rows.length; i += 1) {
      const r = rows[i]
      const avg = Math.round(toNumber(r.avgScore) * 100) / 100
      // 若与上一名平均分相同，则共享名次；否则名次为当前位置 +1
      if (lastAvg !== null && avg === lastAvg) {
        // 共享上一名的 rank
      } else {
        lastRank = i + 1
        lastAvg = avg
      }
      ranked.push({
        studentId: r.studentId,
        studentName: studentNameMap.get(r.studentId)?.name ?? "Unknown",
        averageScore: avg,
        rank: lastRank,
        recordCount: toNumber(r.recordCount),
      })
    }

    return ranked
  }
)

export const getClassStudentsForEntry = cache(
  async (
    classId: string,
    scope?: DataScope
  ): Promise<Array<{ id: string; name: string; email: string }>> => {
    // P3 修复：对 class_taught scope 校验 classId
    if (scope?.type === "class_taught") {
      if (!scope.classIds.includes(classId)) {
        return []
      }
    }

    const studentIds = await getActiveStudentIdsByClassId(classId)
    if (studentIds.length === 0) return []

    const studentNameMap = await getUserNamesByIds(studentIds)

    return studentIds
      .map((id) => {
        const info = studentNameMap.get(id)
        return {
          id,
          name: info?.name ?? "Unknown",
          email: info?.email ?? "",
        }
      })
      .sort((a, b) => a.name.localeCompare(b.name))
  }
)

export const getClassGradeStatsWithMeta = cache(
  async (
    classId: string,
    subjectId?: string,
    examId?: string,
    scope?: DataScope,
    currentUserId?: string
  ): Promise<ClassGradeStats | null> => {
    const classExists = await getClassExists(classId)
    if (!classExists) return null

    const className = await getClassNameById(classId)
    const stats = await getClassGradeStats(classId, subjectId, examId, scope, currentUserId)
    if (!stats) {
      return {
        classId,
        className: className ?? "Unknown",
        stats: {
          average: 0,
          median: 0,
          max: 0,
          min: 0,
          stdDev: 0,
          passRate: 0,
          excellentRate: 0,
          count: 0,
        },
        studentCount: 0,
      }
    }

    const activeStudentIds = await getActiveStudentIdsByClassId(classId)

    return {
      classId,
      className: className ?? "Unknown",
      stats,
      studentCount: activeStudentIds.length,
    }
  }
)
