import "server-only"

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

import { db } from "@/shared/db"
import {
  practiceAnswers,
  practiceSessions,
  questions,
} from "@/shared/db/schema"
import { selectQuestionsForPractice } from "./data-access-strategy"
import { autoGradeAnswer } from "./lib/grading"
import {
  asPracticeAnswerStatus,
  asPracticeSourceMeta,
  asPracticeStatus,
  asPracticeType,
} from "./lib/type-guards"
import { practiceErrors } from "./lib/errors"

import type {
  PracticeAnswerRecord,
  PracticeSessionDetail,
  PracticeSessionSummary,
  PracticeSourceMeta,
  PracticeStats,
  PracticeStatus,
  PracticeType,
} from "./types"

// ---------------------------------------------------------------------------
// 行映射
// ---------------------------------------------------------------------------

function mapSessionRow(row: typeof practiceSessions.$inferSelect): PracticeSessionSummary {
  const answeredQuestions = row.answeredQuestions
  const correctCount = row.correctCount
  return {
    id: row.id,
    studentId: row.studentId,
    subjectId: row.subjectId,
    practiceType: asPracticeType(row.practiceType),
    status: asPracticeStatus(row.status),
    totalQuestions: row.totalQuestions,
    answeredQuestions,
    correctCount,
    accuracy: answeredQuestions > 0 ? correctCount / answeredQuestions : 0,
    startedAt: row.startedAt,
    completedAt: row.completedAt,
    createdAt: row.createdAt,
  }
}

function mapAnswerRow(row: typeof practiceAnswers.$inferSelect & {
  question?: typeof questions.$inferSelect | null
}): PracticeAnswerRecord {
  return {
    id: row.id,
    sessionId: row.sessionId,
    questionId: row.questionId,
    variantContent: row.variantContent,
    isVariant: row.isVariant,
    orderIndex: row.orderIndex,
    status: asPracticeAnswerStatus(row.status),
    studentAnswer: row.studentAnswer,
    isCorrect: row.isCorrect,
    score: row.score,
    maxScore: row.maxScore,
    answeredAt: row.answeredAt,
    question: row.question
      ? {
          id: row.question.id,
          content: row.question.content,
          type: row.question.type,
          difficulty: row.question.difficulty,
        }
      : null,
  }
}

// ---------------------------------------------------------------------------
// 查询：练习会话列表
// ---------------------------------------------------------------------------

export const getPracticeSessions = cache(async (
  studentId: string,
  options?: {
    status?: PracticeStatus
    practiceType?: PracticeType
    page?: number
    pageSize?: number
  },
): Promise<{ data: PracticeSessionSummary[]; total: number }> => {
  const page = options?.page ?? 1
  const pageSize = options?.pageSize ?? 20
  const offset = (page - 1) * pageSize

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

  if (options?.status) {
    conditions.push(eq(practiceSessions.status, options.status))
  }

  if (options?.practiceType) {
    conditions.push(eq(practiceSessions.practiceType, options.practiceType))
  }

  const whereClause = and(...conditions)

  const [totalResult] = await db
    .select({ value: count() })
    .from(practiceSessions)
    .where(whereClause)

  const total = Number(totalResult?.value ?? 0)

  const rows = await db
    .select()
    .from(practiceSessions)
    .where(whereClause)
    .orderBy(desc(practiceSessions.createdAt))
    .limit(pageSize)
    .offset(offset)

  return {
    data: rows.map(mapSessionRow),
    total,
  }
})

// ---------------------------------------------------------------------------
// 查询：练习会话详情（含答题记录）
// ---------------------------------------------------------------------------

export const getPracticeSessionById = cache(async (
  sessionId: string,
  studentId: string,
): Promise<PracticeSessionDetail | null> => {
  const session = await db.query.practiceSessions.findFirst({
    where: and(
      eq(practiceSessions.id, sessionId),
      eq(practiceSessions.studentId, studentId),
    ),
  })

  if (!session) return null

  const answers = await db
    .select()
    .from(practiceAnswers)
    .where(eq(practiceAnswers.sessionId, sessionId))
    .orderBy(practiceAnswers.orderIndex)

  // 批量查询题目内容
  const questionIds = answers.map((a) => a.questionId)
  const questionMap = new Map<string, typeof questions.$inferSelect>()

  if (questionIds.length > 0) {
    const questionRows = await db
      .select()
      .from(questions)
      .where(inArray(questions.id, questionIds))

    for (const q of questionRows) {
      questionMap.set(q.id, q)
    }
  }

  const mappedAnswers: PracticeAnswerRecord[] = answers.map((a) => {
    const question = questionMap.get(a.questionId) ?? null
    return mapAnswerRow({ ...a, question })
  })

  const summary = mapSessionRow(session)

  return {
    ...summary,
    sourceMeta: asPracticeSourceMeta(session.sourceMeta),
    answers: mappedAnswers,
  }
})

// ---------------------------------------------------------------------------
// 查询：练习统计
// ---------------------------------------------------------------------------

export const getPracticeStats = cache(async (studentId: string): Promise<PracticeStats> => {
  const rows = await db
    .select({
      practiceType: practiceSessions.practiceType,
      status: practiceSessions.status,
      totalQuestions: practiceSessions.totalQuestions,
      answeredQuestions: practiceSessions.answeredQuestions,
      correctCount: practiceSessions.correctCount,
    })
    .from(practiceSessions)
    .where(eq(practiceSessions.studentId, studentId))

  const totalSessions = rows.length
  let completedSessions = 0
  let totalQuestionsAnswered = 0
  let totalCorrect = 0

  const byTypeMap = new Map<string, { sessionCount: number; totalQuestions: number; correctCount: number }>()

  for (const row of rows) {
    if (row.status === "completed") {
      completedSessions++
    }
    totalQuestionsAnswered += row.answeredQuestions
    totalCorrect += row.correctCount

    const stat = byTypeMap.get(row.practiceType) ?? { sessionCount: 0, totalQuestions: 0, correctCount: 0 }
    stat.sessionCount++
    stat.totalQuestions += row.totalQuestions
    stat.correctCount += row.correctCount
    byTypeMap.set(row.practiceType, stat)
  }

  const byType = Array.from(byTypeMap.entries()).map(([type, stat]) => ({
    practiceType: asPracticeType(type),
    sessionCount: stat.sessionCount,
    totalQuestions: stat.totalQuestions,
    correctCount: stat.correctCount,
    accuracy: stat.totalQuestions > 0 ? stat.correctCount / stat.totalQuestions : 0,
  }))

  return {
    totalSessions,
    completedSessions,
    totalQuestionsAnswered,
    totalCorrect,
    overallAccuracy: totalQuestionsAnswered > 0 ? totalCorrect / totalQuestionsAnswered : 0,
    byType,
  }
})

// ---------------------------------------------------------------------------
// 写入：创建练习会话
// ---------------------------------------------------------------------------

/**
 * 创建练习会话。
 *
 * 1. 根据练习类型调用出题策略选择题目
 * 2. 创建会话记录
 * 3. 创建答题记录（初始状态为 pending）
 *
 * @throws {PracticeError} 未找到题目时抛 no_questions_found
 *
 * @returns 会话 ID 和选中的题目数量
 */
export async function createPracticeSession(
  studentId: string,
  input: {
    practiceType: PracticeType
    subjectId?: string
    sourceMeta: PracticeSourceMeta
    questionCount?: number
  },
): Promise<{ sessionId: string; selectedCount: number }> {
  const { practiceType, sourceMeta, questionCount = 10 } = input

  // 调用出题策略选择题目
  const selection = await selectQuestionsForPractice(
    studentId,
    practiceType,
    sourceMeta,
    questionCount,
  )

  if (selection.questionIds.length === 0) {
    throw practiceErrors.noQuestionsFound()
  }

  const sessionId = createId()
  const now = new Date()

  // 事务：创建会话 + 答题记录
  await db.transaction(async (tx) => {
    await tx.insert(practiceSessions).values({
      id: sessionId,
      studentId,
      subjectId: input.subjectId ?? null,
      practiceType,
      // sourceMeta 已在上层通过 parseSourceMeta 校验，此处直接写入
      sourceMeta: sourceMeta as unknown,
      status: "in_progress",
      totalQuestions: selection.questionIds.length,
      answeredQuestions: 0,
      correctCount: 0,
      startedAt: now,
    })

    // 批量插入答题记录
    const answerRows = selection.questionIds.map((questionId, index) => ({
      id: createId(),
      sessionId,
      studentId,
      questionId,
      variantContent: selection.variants.get(questionId) ?? null,
      isVariant: selection.variants.has(questionId),
      orderIndex: index,
      status: "pending" as const,
      maxScore: 1,
    }))

    await tx.insert(practiceAnswers).values(answerRows)
  })

  return { sessionId, selectedCount: selection.questionIds.length }
}

// ---------------------------------------------------------------------------
// 写入：提交单题答案
// ---------------------------------------------------------------------------

/**
 * 提交单题答案并自动判分。
 *
 * 自动判分逻辑：
 * - 选择题/判断题：通过 extractCorrectAnswer 比对答案
 * - 填空题：暂不自动判分（isCorrect = null）
 *
 * 并发安全：整个校验+判分+统计更新流程包裹在事务中，
 * 对答题记录加行锁（SELECT ... FOR UPDATE），
 * 防止同一答案被并发重复判分导致统计累加错误。
 *
 * @throws {PracticeError} 会话/答题记录不存在、已结束、已作答时抛对应错误码
 *
 * @returns 是否判分成功
 */
export async function submitPracticeAnswer(
  sessionId: string,
  studentId: string,
  answerId: string,
  answer: unknown,
  skip: boolean = false,
): Promise<{ isCorrect: boolean | null; score: number | null }> {
  // 事务：行锁 + 校验 + 判分 + 统计更新（防止并发重复判分）
  return await db.transaction(async (tx) => {
    // 1. 校验会话归属（带行锁）
    const [session] = await tx
      .select()
      .from(practiceSessions)
      .where(and(
        eq(practiceSessions.id, sessionId),
        eq(practiceSessions.studentId, studentId),
      ))
      .for("update")

    if (!session) {
      throw practiceErrors.sessionNotFound()
    }

    if (session.status !== "in_progress") {
      throw practiceErrors.sessionEnded()
    }

    // 2. 查询答题记录（带行锁，防止并发重复提交）
    const [answerRecord] = await tx
      .select()
      .from(practiceAnswers)
      .where(and(
        eq(practiceAnswers.id, answerId),
        eq(practiceAnswers.sessionId, sessionId),
      ))
      .for("update")

    if (!answerRecord) {
      throw practiceErrors.answerNotFound()
    }

    if (answerRecord.status === "answered") {
      throw practiceErrors.answerAlreadySubmitted()
    }

    const now = new Date()

    if (skip) {
      // 跳过此题：状态置为 skipped，不累加已答题数与正确数
      await tx
        .update(practiceAnswers)
        .set({
          status: "skipped",
          answeredAt: now,
        })
        .where(eq(practiceAnswers.id, answerId))

      // 累加已答题数（不累加正确数）
      await tx
        .update(practiceSessions)
        .set({
          answeredQuestions: session.answeredQuestions + 1,
        })
        .where(eq(practiceSessions.id, sessionId))

      return { isCorrect: null, score: null }
    }

    // 3. 自动判分：查询题目内容并提取正确答案
    const [question] = await tx
      .select()
      .from(questions)
      .where(eq(questions.id, answerRecord.questionId))
      .limit(1)

    if (!question) {
      throw practiceErrors.questionNotFound()
    }

    // 如果是变式题，使用变式题内容
    const contentToUse = answerRecord.variantContent ?? question.content
    const isCorrect = autoGradeAnswer(question.type, contentToUse, answer)

    const score = isCorrect === true ? answerRecord.maxScore : (isCorrect === false ? 0 : null)

    // 4. 更新答题记录
    await tx
      .update(practiceAnswers)
      .set({
        status: "answered",
        studentAnswer: answer,
        isCorrect,
        score,
        answeredAt: now,
      })
      .where(eq(practiceAnswers.id, answerId))

    // 5. 累加会话统计（基于步骤 1 已加锁的 session 行）
    await tx
      .update(practiceSessions)
      .set({
        answeredQuestions: session.answeredQuestions + 1,
        correctCount: session.correctCount + (isCorrect === true ? 1 : 0),
      })
      .where(eq(practiceSessions.id, sessionId))

    return { isCorrect, score }
  })
}

// ---------------------------------------------------------------------------
// 写入：完成/放弃练习会话
// ---------------------------------------------------------------------------

/**
 * 完成练习会话。
 *
 * 完整性校验：必须答完所有题目（answeredQuestions === totalQuestions）才能完成，
 * 防止学生提前完成导致统计失真。
 *
 * 注意：跳过的题目也算"已作答"（status=skipped），与 answeredQuestions 累加逻辑一致。
 *
 * @throws {PracticeError} 会话不存在 → session_not_found；未答完 → session_not_complete
 */
export async function completePracticeSession(
  sessionId: string,
  studentId: string,
): Promise<void> {
  const session = await db.query.practiceSessions.findFirst({
    where: and(
      eq(practiceSessions.id, sessionId),
      eq(practiceSessions.studentId, studentId),
    ),
  })

  if (!session) {
    throw practiceErrors.sessionNotFound()
  }

  if (session.status !== "in_progress") {
    // 已完成或已放弃，幂等返回
    return
  }

  // 完整性校验：必须答完所有题目
  if (session.answeredQuestions !== session.totalQuestions) {
    throw practiceErrors.sessionNotComplete()
  }

  await db
    .update(practiceSessions)
    .set({
      status: "completed",
      completedAt: new Date(),
    })
    .where(eq(practiceSessions.id, sessionId))
}

/**
 * 放弃练习会话。
 *
 * 幂等：已完成或已放弃的会话再次调用不会报错。
 *
 * @throws {PracticeError} 会话不存在 → session_not_found
 */
export async function abandonPracticeSession(
  sessionId: string,
  studentId: string,
): Promise<void> {
  const session = await db.query.practiceSessions.findFirst({
    where: and(
      eq(practiceSessions.id, sessionId),
      eq(practiceSessions.studentId, studentId),
    ),
  })

  if (!session) {
    throw practiceErrors.sessionNotFound()
  }

  if (session.status !== "in_progress") {
    // 已完成或已放弃，幂等返回
    return
  }

  await db
    .update(practiceSessions)
    .set({
      status: "abandoned",
      completedAt: new Date(),
    })
    .where(eq(practiceSessions.id, sessionId))
}

// ---------------------------------------------------------------------------
// 内部辅助函数
// ---------------------------------------------------------------------------

// 自动判分、答案归一化等纯函数已抽取至 lib/grading.ts，便于单测与复用。
// 会话统计累加逻辑已内联到 submitPracticeSession 事务中，确保原子性。
