import "server-only"

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

import { db } from "@/shared/db"
import { gradeAppeals, gradeRecords } from "@/shared/db/schema"

/**
 * P3-7 成绩申诉数据访问层。
 *
 * 申诉状态机：pending → approved/rejected/withdrawn
 * 一条 grade_record 同时只允许一条 pending 申诉（应用层校验）。
 * 成绩修改全程留痕：originalScore 快照 + adjustedScore + reviewComment。
 */

export interface GradeAppeal {
  id: string
  gradeRecordId: string
  studentId: string
  originalScore: string
  expectedScore: string | null
  reason: string
  status: "pending" | "approved" | "rejected" | "withdrawn"
  reviewerId: string | null
  reviewComment: string | null
  adjustedScore: string | null
  reviewedAt: string | null
  createdAt: string
  updatedAt: string
}

export interface GradeAppealWithRecord extends GradeAppeal {
  gradeRecordTitle: string
  gradeRecordSubjectId: string
  gradeRecordClassId: string
  studentName: string | null
  reviewerName: string | null
}

/** 类型转换：DB 行 → 业务对象 */
function serializeAppeal(row: typeof gradeAppeals.$inferSelect): GradeAppeal {
  return {
    id: row.id,
    gradeRecordId: row.gradeRecordId,
    studentId: row.studentId,
    originalScore: row.originalScore,
    expectedScore: row.expectedScore,
    reason: row.reason,
    status: row.status,
    reviewerId: row.reviewerId,
    reviewComment: row.reviewComment,
    adjustedScore: row.adjustedScore,
    reviewedAt: row.reviewedAt ? row.reviewedAt.toISOString() : null,
    createdAt: row.createdAt.toISOString(),
    updatedAt: row.updatedAt.toISOString(),
  }
}

/**
 * 检查某成绩记录是否已有 pending 申诉。
 * 用于提交前校验，避免重复申诉。
 */
export async function hasPendingAppeal(gradeRecordId: string): Promise<boolean> {
  const existing = await db.query.gradeAppeals.findFirst({
    where: and(
      eq(gradeAppeals.gradeRecordId, gradeRecordId),
      eq(gradeAppeals.status, "pending")
    ),
  })
  return Boolean(existing)
}

/**
 * 创建成绩申诉。
 * 调用方需先校验：1) 成绩存在且属于当前学生；2) 无 pending 申诉。
 */
export async function createGradeAppeal(params: {
  gradeRecordId: string
  studentId: string
  originalScore: string
  reason: string
  expectedScore?: number
}): Promise<string> {
  const id = createId()
  await db.insert(gradeAppeals).values({
    id,
    gradeRecordId: params.gradeRecordId,
    studentId: params.studentId,
    originalScore: params.originalScore,
    expectedScore: params.expectedScore !== undefined ? String(params.expectedScore) : null,
    reason: params.reason,
    status: "pending",
  })
  return id
}

/**
 * 获取学生自己的申诉列表（按创建时间倒序）。
 */
export const getStudentAppeals = cache(
  async (studentId: string): Promise<GradeAppeal[]> => {
    const rows = await db
      .select()
      .from(gradeAppeals)
      .where(eq(gradeAppeals.studentId, studentId))
      .orderBy(desc(gradeAppeals.createdAt))

    return rows.map(serializeAppeal)
  }
)

/**
 * 获取教师待复核的申诉列表（按创建时间正序，先到先审）。
 * 仅返回 pending 状态的申诉。
 */
export const getPendingAppealsForReview = cache(
  async (classIds: string[]): Promise<GradeAppealWithRecord[]> => {
    if (classIds.length === 0) return []

    const rows = await db
      .select({
        appeal: gradeAppeals,
        gradeRecord: gradeRecords,
      })
      .from(gradeAppeals)
      .innerJoin(gradeRecords, eq(gradeAppeals.gradeRecordId, gradeRecords.id))
      .where(
        and(
          eq(gradeAppeals.status, "pending"),
          // 通过 gradeRecord.classId 过滤（教师只能看自己班级的申诉）
        )
      )
      .orderBy(gradeAppeals.createdAt)

    // 在 JS 层过滤班级范围（避免复杂 SQL join）
    const filtered = rows.filter((r) => classIds.includes(r.gradeRecord.classId))

    return filtered.map((r) => ({
      ...serializeAppeal(r.appeal),
      gradeRecordTitle: r.gradeRecord.title,
      gradeRecordSubjectId: r.gradeRecord.subjectId,
      gradeRecordClassId: r.gradeRecord.classId,
      studentName: null,
      reviewerName: null,
    }))
  }
)

/**
 * 获取单个申诉详情（含成绩信息）。
 * 调用方需校验：学生只能看自己的申诉，教师只能看自己班级的申诉。
 */
export const getAppealById = cache(
  async (appealId: string): Promise<GradeAppealWithRecord | null> => {
    const rows = await db
      .select({
        appeal: gradeAppeals,
        gradeRecord: gradeRecords,
      })
      .from(gradeAppeals)
      .innerJoin(gradeRecords, eq(gradeAppeals.gradeRecordId, gradeRecords.id))
      .where(eq(gradeAppeals.id, appealId))
      .limit(1)

    if (rows.length === 0) return null
    const row = rows[0]
    return {
      ...serializeAppeal(row.appeal),
      gradeRecordTitle: row.gradeRecord.title,
      gradeRecordSubjectId: row.gradeRecord.subjectId,
      gradeRecordClassId: row.gradeRecord.classId,
      studentName: null,
      reviewerName: null,
    }
  }
)

/**
 * 教师复核申诉（状态机转换：pending → approved/rejected）。
 *
 * 若 decision=approved 且 adjustedScore 有值：
 * 1. 更新申诉记录（reviewerId, reviewComment, adjustedScore, reviewedAt, status）
 * 2. 同步更新 grade_records.score（留痕：originalScore 已在申诉记录中快照）
 *
 * 若 decision=rejected：
 * 1. 仅更新申诉记录，不修改成绩
 */
export async function reviewGradeAppeal(params: {
  appealId: string
  reviewerId: string
  decision: "approved" | "rejected"
  reviewComment: string
  adjustedScore?: number
}): Promise<void> {
  const { appealId, reviewerId, decision, reviewComment, adjustedScore } = params

  const appeal = await db.query.gradeAppeals.findFirst({
    where: eq(gradeAppeals.id, appealId),
  })

  if (!appeal) {
    throw new Error("Appeal not found")
  }
  if (appeal.status !== "pending") {
    throw new Error("Appeal is not pending")
  }

  const now = new Date()

  // 更新申诉记录
  await db
    .update(gradeAppeals)
    .set({
      status: decision,
      reviewerId,
      reviewComment,
      adjustedScore: adjustedScore !== undefined ? String(adjustedScore) : null,
      reviewedAt: now,
    })
    .where(eq(gradeAppeals.id, appealId))

  // 若批准且分数有变，同步更新成绩记录
  if (decision === "approved" && adjustedScore !== undefined) {
    await db
      .update(gradeRecords)
      .set({ score: String(adjustedScore) })
      .where(eq(gradeRecords.id, appeal.gradeRecordId))
  }
}

/**
 * 学生撤回申诉（状态机转换：pending → withdrawn）。
 */
export async function withdrawGradeAppeal(params: {
  appealId: string
  studentId: string
}): Promise<void> {
  const { appealId, studentId } = params

  const appeal = await db.query.gradeAppeals.findFirst({
    where: and(
      eq(gradeAppeals.id, appealId),
      eq(gradeAppeals.studentId, studentId)
    ),
  })

  if (!appeal) {
    throw new Error("Appeal not found or not owned by student")
  }
  if (appeal.status !== "pending") {
    throw new Error("Appeal is not pending")
  }

  await db
    .update(gradeAppeals)
    .set({ status: "withdrawn" })
    .where(eq(gradeAppeals.id, appealId))
}
