import "server-only"

import { and, asc, count, desc, eq, gte, inArray, lte, or, sql, type SQL } from "drizzle-orm"
import { createId } from "@paralleldrive/cuid2"

import { db } from "@/shared/db"
import {
  attendanceRecords,
  attendanceRules,
  classes,
  users,
} from "@/shared/db/schema"
import { getClassActiveStudentsWithInfo } from "@/modules/classes/data-access"
import type { DataScope } from "@/shared/types/permissions"

import type {
  AttendanceListItem,
  AttendanceQueryParams,
  AttendanceRule,
  PaginatedAttendanceResult,
} from "./types"
import type {
  AttendanceRuleInput,
  BatchRecordAttendanceInput,
  RecordAttendanceInput,
  UpdateAttendanceInput,
} from "./schema"

const buildScopeFilter = (scope: DataScope): SQL | null => {
  if (scope.type === "all") return null
  if (scope.type === "class_taught") {
    return scope.classIds.length > 0
      ? inArray(attendanceRecords.classId, scope.classIds)
      : sql`1=0`
  }
  if (scope.type === "grade_managed") return sql`1=0`
  if (scope.type === "class_members") return null
  if (scope.type === "children") {
    return scope.childrenIds.length > 0
      ? inArray(attendanceRecords.studentId, scope.childrenIds)
      : sql`1=0`
  }
  if (scope.type === "owned") return eq(attendanceRecords.studentId, scope.userId)
  return sql`1=0`
}

const serializeDate = (d: Date | string | null): string =>
  d ? new Date(d).toISOString().slice(0, 10) : ""

const mapListItem = (
  r: typeof attendanceRecords.$inferSelect,
  studentName: string | null,
  className: string | null,
  recorderName: string
): AttendanceListItem => ({
  id: r.id,
  studentId: r.studentId,
  studentName: studentName ?? "Unknown",
  classId: r.classId,
  className: className ?? "Unknown",
  scheduleId: r.scheduleId ?? null,
  date: serializeDate(r.date),
  status: r.status,
  remark: r.remark ?? null,
  recordedBy: r.recordedBy,
  recorderName,
  createdAt: r.createdAt.toISOString(),
})

const resolveRecorderNames = async (
  rows: { record: typeof attendanceRecords.$inferSelect }[]
): Promise<Map<string, string>> => {
  const ids = Array.from(new Set(rows.map((r) => r.record.recordedBy)))
  const map = new Map<string, string>()
  if (ids.length > 0) {
    const recorders = await db
      .select({ id: users.id, name: users.name })
      .from(users)
      .where(inArray(users.id, ids))
    for (const r of recorders) map.set(r.id, r.name ?? "Unknown")
  }
  return map
}

export async function getAttendanceRecords(
  params: AttendanceQueryParams & { scope: DataScope; currentUserId?: string }
): Promise<PaginatedAttendanceResult> {
  const page = Math.max(1, params.page ?? 1)
  const pageSize = Math.max(1, Math.min(100, params.pageSize ?? 20))
  const conditions: SQL[] = []

  const scopeFilter = buildScopeFilter(params.scope)
  if (scopeFilter) conditions.push(scopeFilter)
  if (params.scope.type === "class_members" && params.currentUserId) {
    conditions.push(eq(attendanceRecords.studentId, params.currentUserId))
  }
  if (params.classId) conditions.push(eq(attendanceRecords.classId, params.classId))
  if (params.studentId) conditions.push(eq(attendanceRecords.studentId, params.studentId))
  if (params.date) conditions.push(eq(attendanceRecords.date, new Date(params.date)))
  if (params.startDate) conditions.push(gte(attendanceRecords.date, new Date(params.startDate)))
  if (params.endDate) conditions.push(lte(attendanceRecords.date, new Date(params.endDate)))
  if (params.status) conditions.push(eq(attendanceRecords.status, params.status))

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

  const [totalRow] = await db.select({ c: count() }).from(attendanceRecords).where(where)
  const total = Number(totalRow?.c ?? 0)
  const totalPages = Math.max(1, Math.ceil(total / pageSize))

  const rows = await db
    .select({
      record: attendanceRecords,
      studentName: users.name,
      className: classes.name,
    })
    .from(attendanceRecords)
    .leftJoin(users, eq(users.id, attendanceRecords.studentId))
    .leftJoin(classes, eq(classes.id, attendanceRecords.classId))
    .where(where)
    .orderBy(desc(attendanceRecords.date), desc(attendanceRecords.createdAt))
    .limit(pageSize)
    .offset((page - 1) * pageSize)

  const recorderMap = await resolveRecorderNames(rows)

  return {
    items: rows.map((r) =>
      mapListItem(r.record, r.studentName, r.className, recorderMap.get(r.record.recordedBy) ?? "Unknown")
    ),
    total,
    page,
    pageSize,
    totalPages,
  }
}

export async function getClassAttendanceForDate(
  classId: string,
  date: string
): Promise<AttendanceListItem[]> {
  const rows = await db
    .select({
      record: attendanceRecords,
      studentName: users.name,
      className: classes.name,
    })
    .from(attendanceRecords)
    .leftJoin(users, eq(users.id, attendanceRecords.studentId))
    .leftJoin(classes, eq(classes.id, attendanceRecords.classId))
    .where(and(eq(attendanceRecords.classId, classId), eq(attendanceRecords.date, new Date(date))))
    .orderBy(asc(users.name))

  return rows.map((r) => mapListItem(r.record, r.studentName, r.className, "Unknown"))
}

export async function createAttendanceRecord(
  data: RecordAttendanceInput,
  recordedBy: string
): Promise<string> {
  const id = createId()
  await db.insert(attendanceRecords).values({
    id,
    studentId: data.studentId,
    classId: data.classId,
    scheduleId: data.scheduleId ?? null,
    date: new Date(data.date),
    status: data.status,
    remark: data.remark ?? null,
    recordedBy,
  })
  return id
}

export async function batchCreateAttendanceRecords(
  data: BatchRecordAttendanceInput,
  recordedBy: string
): Promise<number> {
  if (data.records.length === 0) return 0
  const rows = data.records.map((r) => ({
    id: createId(),
    studentId: r.studentId,
    classId: r.classId,
    scheduleId: r.scheduleId ?? null,
    date: new Date(r.date),
    status: r.status,
    remark: r.remark ?? null,
    recordedBy,
  }))
  await db.insert(attendanceRecords).values(rows)
  return rows.length
}

export async function updateAttendanceRecord(
  id: string,
  data: UpdateAttendanceInput
): Promise<void> {
  const update: Partial<typeof attendanceRecords.$inferSelect> = { updatedAt: new Date() }
  if (data.status !== undefined) update.status = data.status
  if (data.remark !== undefined) update.remark = data.remark
  if (data.scheduleId !== undefined) update.scheduleId = data.scheduleId
  await db.update(attendanceRecords).set(update).where(eq(attendanceRecords.id, id))
}

export async function deleteAttendanceRecord(id: string): Promise<void> {
  await db.delete(attendanceRecords).where(eq(attendanceRecords.id, id))
}

/**
 * 获取考勤记录的 classId（用于资源归属校验）。
 * 返回 null 表示记录不存在。
 */
export async function getAttendanceRecordClassId(id: string): Promise<string | null> {
  const [row] = await db
    .select({ classId: attendanceRecords.classId })
    .from(attendanceRecords)
    .where(eq(attendanceRecords.id, id))
    .limit(1)
  return row?.classId ?? null
}

export async function getClassStudentsForAttendance(
  classId: string
): Promise<Array<{ id: string; name: string; email: string }>> {
  // 通过 classes data-access 获取班级学生，避免跨模块直查 classEnrollments 表
  return getClassActiveStudentsWithInfo(classId)
}

export async function getAttendanceRules(classId?: string): Promise<AttendanceRule[]> {
  const conditions: SQL[] = []
  if (classId) {
    const classCondition = or(eq(attendanceRules.classId, classId), sql`${attendanceRules.classId} IS NULL`)
    if (classCondition) conditions.push(classCondition)
  }
  const rows = await db
    .select()
    .from(attendanceRules)
    .where(conditions.length > 0 ? and(...conditions) : undefined)
    .orderBy(desc(attendanceRules.createdAt))

  return rows.map((r) => ({
    id: r.id,
    classId: r.classId ?? null,
    lateThresholdMinutes: r.lateThresholdMinutes ?? null,
    earlyLeaveThresholdMinutes: r.earlyLeaveThresholdMinutes ?? null,
    enableAutoMark: r.enableAutoMark ?? null,
    createdAt: r.createdAt.toISOString(),
    updatedAt: r.updatedAt.toISOString(),
  }))
}

export async function upsertAttendanceRules(data: AttendanceRuleInput): Promise<string> {
  const [existing] = await db
    .select()
    .from(attendanceRules)
    .where(eq(attendanceRules.classId, data.classId))
    .limit(1)

  if (existing) {
    await db
      .update(attendanceRules)
      .set({
        lateThresholdMinutes: data.lateThresholdMinutes ?? 15,
        earlyLeaveThresholdMinutes: data.earlyLeaveThresholdMinutes ?? 15,
        enableAutoMark: data.enableAutoMark ?? false,
        updatedAt: new Date(),
      })
      .where(eq(attendanceRules.id, existing.id))
    return existing.id
  }

  const id = createId()
  await db.insert(attendanceRules).values({
    id,
    classId: data.classId,
    lateThresholdMinutes: data.lateThresholdMinutes ?? 15,
    earlyLeaveThresholdMinutes: data.earlyLeaveThresholdMinutes ?? 15,
    enableAutoMark: data.enableAutoMark ?? false,
  })
  return id
}

export type AttendanceOverviewStats = {
  totalRecords: number
  presentCount: number
  absentCount: number
  lateCount: number
  earlyLeaveCount: number
  excusedCount: number
  attendanceRate: number
}

export async function getAttendanceStats(params: {
  scope: DataScope
  currentUserId: string
  classId?: string
  date?: string
}): Promise<AttendanceOverviewStats> {
  // 直接使用 SQL 聚合查询，避免分页截断导致统计失真（P0 修复）
  const conditions: SQL[] = []

  const scopeFilter = buildScopeFilter(params.scope)
  if (scopeFilter) conditions.push(scopeFilter)
  if (params.scope.type === "class_members" && params.currentUserId) {
    conditions.push(eq(attendanceRecords.studentId, params.currentUserId))
  }
  if (params.classId) conditions.push(eq(attendanceRecords.classId, params.classId))
  if (params.date) conditions.push(eq(attendanceRecords.date, new Date(params.date)))

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

  const [row] = await db
    .select({
      totalRecords: count(),
      presentCount: sql<number>`COALESCE(SUM(CASE WHEN ${attendanceRecords.status} = 'present' THEN 1 ELSE 0 END), 0)`,
      absentCount: sql<number>`COALESCE(SUM(CASE WHEN ${attendanceRecords.status} = 'absent' THEN 1 ELSE 0 END), 0)`,
      lateCount: sql<number>`COALESCE(SUM(CASE WHEN ${attendanceRecords.status} = 'late' THEN 1 ELSE 0 END), 0)`,
      earlyLeaveCount: sql<number>`COALESCE(SUM(CASE WHEN ${attendanceRecords.status} = 'early_leave' THEN 1 ELSE 0 END), 0)`,
      excusedCount: sql<number>`COALESCE(SUM(CASE WHEN ${attendanceRecords.status} = 'excused' THEN 1 ELSE 0 END), 0)`,
    })
    .from(attendanceRecords)
    .where(where)

  const total = Number(row?.totalRecords ?? 0)
  const present = Number(row?.presentCount ?? 0)
  const absent = Number(row?.absentCount ?? 0)
  const late = Number(row?.lateCount ?? 0)
  const earlyLeave = Number(row?.earlyLeaveCount ?? 0)
  const excused = Number(row?.excusedCount ?? 0)

  return {
    totalRecords: total,
    presentCount: present,
    absentCount: absent,
    lateCount: late,
    earlyLeaveCount: earlyLeave,
    excusedCount: excused,
    attendanceRate: total > 0 ? Math.round((present / total) * 1000) / 10 : 0,
  }
}
