import "server-only"

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

import { db } from "@/shared/db"
import {
  errorBookItems,
  errorBookReviews,
  questionsToKnowledgePoints,
} from "@/shared/db/schema"
import {
  calculateNewInterval,
  calculateNewMastery,
  deriveStatus,
  calculateNextReviewAt,
  calculateNewCorrectStreak,
} from "./sm2-algorithm"

import type {
  ErrorBookItem,
  ErrorBookItemDetail,
  ErrorBookListResult,
  ErrorBookReviewRecord,
  ErrorBookStats,
  ErrorBookStatusValue,
  GetErrorBookItemsParams,
} from "./types"
import type { ErrorBookReviewResult } from "./schema"

// ---------------------------------------------------------------------------
// 类型守卫
// ---------------------------------------------------------------------------

const isReviewResult = (v: unknown): v is ErrorBookReviewResult =>
  v === "again" || v === "hard" || v === "good" || v === "easy"

const toReviewResult = (v: string | null | undefined): ErrorBookReviewResult =>
  isReviewResult(v) ? v : "again"

const isStatus = (v: unknown): v is ErrorBookStatusValue =>
  v === "new" || v === "learning" || v === "mastered" || v === "archived"

/** 将数据库状态字符串转换为 ErrorBookStatusValue，无效值回退为 "new" */
export const toStatus = (v: string | null | undefined): ErrorBookStatusValue =>
  isStatus(v) ? v : "new"

/** 类型守卫：判断未知值是否为 string[] */
function isStringArray(v: unknown): v is string[] {
  return Array.isArray(v) && v.every((item) => typeof item === "string")
}

/** 将未知值安全转换为 string[] | null（用于 JSON 列字段） */
export function toStringArray(v: unknown): string[] | null {
  return isStringArray(v) ? v : null
}

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

/** 错题条目查询结果类型（含关联的 question/subject 字段） */
type ErrorBookItemWithRelations = typeof errorBookItems.$inferSelect & {
  question?: { id: string; content: unknown; type: string; difficulty: number | null } | null
  subject?: { id: string; name: string } | null
}

function mapRowToItem(row: ErrorBookItemWithRelations): ErrorBookItem {
  return {
    id: row.id,
    studentId: row.studentId,
    questionId: row.questionId,
    sourceType: row.sourceType,
    sourceId: row.sourceId,
    studentAnswer: row.studentAnswer,
    correctAnswer: row.correctAnswer,
    subjectId: row.subjectId,
    knowledgePointIds: toStringArray(row.knowledgePointIds),
    status: toStatus(row.status),
    masteryLevel: row.masteryLevel,
    nextReviewAt: row.nextReviewAt,
    reviewInterval: row.reviewInterval,
    reviewCount: row.reviewCount,
    correctStreak: row.correctStreak,
    note: row.note,
    errorTags: toStringArray(row.errorTags),
    createdAt: row.createdAt,
    updatedAt: row.updatedAt,
    question: row.question
      ? {
          id: row.question.id,
          content: row.question.content,
          type: row.question.type,
          difficulty: row.question.difficulty,
        }
      : null,
    subjectName: row.subject?.name ?? null,
  }
}

// ---------------------------------------------------------------------------
// 查询：错题本列表
// ---------------------------------------------------------------------------

export const getErrorBookItems = cache(async (params: GetErrorBookItemsParams): Promise<ErrorBookListResult> => {
  const { studentId, q, page = 1, pageSize = 20, status, sourceType, subjectId, dueOnly } = params
  const offset = (page - 1) * pageSize

  const conditions: SQL[] = [eq(errorBookItems.studentId, studentId)]

  if (status) {
    conditions.push(eq(errorBookItems.status, status))
  }

  if (sourceType) {
    conditions.push(eq(errorBookItems.sourceType, sourceType))
  }

  if (subjectId) {
    conditions.push(eq(errorBookItems.subjectId, subjectId))
  }

  if (dueOnly) {
    const now = new Date()
    const dueCondition = or(
      isNull(errorBookItems.nextReviewAt),
      lte(errorBookItems.nextReviewAt, now)
    )
    if (dueCondition) {
      conditions.push(dueCondition)
    }
  }

  if (q && q.trim().length > 0) {
    const needle = `%${q.trim().toLowerCase()}%`
    conditions.push(sql`LOWER(CAST(${errorBookItems.note} AS CHAR)) LIKE ${needle}`)
  }

  const whereClause = and(...conditions)

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

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

  const rows = await db.query.errorBookItems.findMany({
    where: whereClause,
    limit: pageSize,
    offset,
    orderBy: [desc(errorBookItems.createdAt)],
    with: {
      question: {
        columns: {
          id: true,
          content: true,
          type: true,
          difficulty: true,
        },
      },
      subject: {
        columns: {
          id: true,
          name: true,
        },
      },
    },
  })

  return {
    data: rows.map((row) => mapRowToItem(row)),
    meta: {
      page,
      pageSize,
      total,
      totalPages: Math.ceil(total / pageSize),
    },
  }
})

// ---------------------------------------------------------------------------
// 查询：错题详情
// ---------------------------------------------------------------------------

export const getErrorBookItemById = cache(async (
  itemId: string,
  studentId: string
): Promise<ErrorBookItemDetail | null> => {
  const row = await db.query.errorBookItems.findFirst({
    where: and(
      eq(errorBookItems.id, itemId),
      eq(errorBookItems.studentId, studentId)
    ),
    with: {
      question: {
        columns: {
          id: true,
          content: true,
          type: true,
          difficulty: true,
        },
      },
      subject: {
        columns: {
          id: true,
          name: true,
        },
      },
      reviews: {
        orderBy: [desc(errorBookReviews.reviewedAt)],
        limit: 20,
      },
    },
  })

  if (!row) return null

  const base = mapRowToItem(row)
  const reviews: ErrorBookReviewRecord[] = (row.reviews ?? []).map((r) => ({
    id: r.id,
    result: toReviewResult(r.result),
    reviewedAt: r.reviewedAt,
    newInterval: r.newInterval,
    newMasteryLevel: r.newMasteryLevel,
  }))

  return { ...base, reviews }
})

// ---------------------------------------------------------------------------
// 查询：错题本统计（SQL 聚合优化）
// ---------------------------------------------------------------------------

export const getErrorBookStats = cache(async (studentId: string): Promise<ErrorBookStats> => {
  const now = new Date()

  // 使用 SQL GROUP BY 聚合状态计数
  const statusCounts = await db
    .select({
      status: errorBookItems.status,
      count: count(),
    })
    .from(errorBookItems)
    .where(eq(errorBookItems.studentId, studentId))
    .groupBy(errorBookItems.status)

  let totalCount = 0
  let newCount = 0
  let learningCount = 0
  let masteredCount = 0
  let archivedCount = 0

  for (const row of statusCounts) {
    const status = toStatus(row.status)
    totalCount += row.count
    if (status === "new") newCount = row.count
    else if (status === "learning") learningCount = row.count
    else if (status === "mastered") masteredCount = row.count
    else if (status === "archived") archivedCount = row.count
  }

  // 待复习数：单独查询（status NOT IN mastered/archived AND due）
  const dueConditions: SQL[] = [
    eq(errorBookItems.studentId, studentId),
    not(inArray(errorBookItems.status, ["mastered", "archived"])),
  ]
  const dueCondition = or(
    isNull(errorBookItems.nextReviewAt),
    lte(errorBookItems.nextReviewAt, now)
  )
  if (dueCondition) {
    dueConditions.push(dueCondition)
  }

  const dueReviewResult = await db
    .select({ value: count() })
    .from(errorBookItems)
    .where(and(...dueConditions))

  const dueReviewCount = Number(dueReviewResult[0]?.value ?? 0)

  return {
    totalCount,
    newCount,
    learningCount,
    masteredCount,
    archivedCount,
    dueReviewCount,
    masteredRate: totalCount > 0 ? masteredCount / totalCount : 0,
  }
})

// ---------------------------------------------------------------------------
// 写入：手动添加错题
// ---------------------------------------------------------------------------

export async function createErrorBookItem(
  studentId: string,
  input: {
    questionId: string
    studentAnswer?: unknown
    correctAnswer?: unknown
    subjectId?: string
    knowledgePointIds?: string[]
    note?: string
    errorTags?: string[]
  }
): Promise<string> {
  const newId = createId()
  const now = new Date()

  // 如果未提供知识点，从题目关联中查询
  let knowledgePointIds = input.knowledgePointIds
  if (!knowledgePointIds || knowledgePointIds.length === 0) {
    const kps = await db
      .select({ id: questionsToKnowledgePoints.knowledgePointId })
      .from(questionsToKnowledgePoints)
      .where(eq(questionsToKnowledgePoints.questionId, input.questionId))
    knowledgePointIds = kps.map((k) => k.id)
  }

  // 如果未提供学科，从题目关联中查询（暂留空，学科由调用方提供）

  await db.insert(errorBookItems).values({
    id: newId,
    studentId,
    questionId: input.questionId,
    sourceType: "manual",
    sourceId: null,
    studentAnswer: input.studentAnswer ?? null,
    correctAnswer: input.correctAnswer ?? null,
    subjectId: input.subjectId ?? null,
    knowledgePointIds: knowledgePointIds ?? null,
    status: "new",
    masteryLevel: 0,
    nextReviewAt: now, // 立即可复习
    reviewInterval: 1,
    reviewCount: 0,
    correctStreak: 0,
    note: input.note ?? null,
    errorTags: input.errorTags ?? null,
  })

  return newId
}

// ---------------------------------------------------------------------------
// 写入：更新笔记
// ---------------------------------------------------------------------------

export async function updateErrorBookNote(
  itemId: string,
  studentId: string,
  input: { note?: string; errorTags?: string[] }
): Promise<void> {
  await db
    .update(errorBookItems)
    .set({
      ...(input.note !== undefined ? { note: input.note } : {}),
      ...(input.errorTags !== undefined ? { errorTags: input.errorTags } : {}),
      updatedAt: new Date(),
    })
    .where(
      and(
        eq(errorBookItems.id, itemId),
        eq(errorBookItems.studentId, studentId)
      )
    )
}

// ---------------------------------------------------------------------------
// 写入：记录复习结果（SM-2 算法）
// ---------------------------------------------------------------------------

export async function recordReview(
  itemId: string,
  studentId: string,
  result: ErrorBookReviewResult
): Promise<void> {
  const item = await db.query.errorBookItems.findFirst({
    where: and(
      eq(errorBookItems.id, itemId),
      eq(errorBookItems.studentId, studentId)
    ),
  })

  if (!item) throw new Error("错题条目不存在")

  const newInterval = calculateNewInterval(item.reviewInterval, result, item.reviewCount)
  const newStreak = calculateNewCorrectStreak(item.correctStreak, result)
  const newMastery = calculateNewMastery(item.masteryLevel, result, newStreak)
  const newStatus = deriveStatus(newMastery, newStreak)
  const nextReviewAt = newStatus === "mastered" ? null : calculateNextReviewAt(newInterval)

  await db.transaction(async (tx) => {
    await tx.insert(errorBookReviews).values({
      id: createId(),
      itemId,
      studentId,
      result,
      reviewedAt: new Date(),
      newInterval,
      newMasteryLevel: newMastery,
    })

    await tx
      .update(errorBookItems)
      .set({
        status: newStatus,
        masteryLevel: newMastery,
        reviewInterval: newInterval,
        reviewCount: item.reviewCount + 1,
        correctStreak: newStreak,
        nextReviewAt,
        updatedAt: new Date(),
      })
      .where(eq(errorBookItems.id, itemId))
  })
}

// ---------------------------------------------------------------------------
// 写入：删除错题
// ---------------------------------------------------------------------------

export async function deleteErrorBookItem(itemId: string, studentId: string): Promise<void> {
  await db
    .delete(errorBookItems)
    .where(
      and(
        eq(errorBookItems.id, itemId),
        eq(errorBookItems.studentId, studentId)
      )
    )
}

// ---------------------------------------------------------------------------
// 写入：归档错题
// ---------------------------------------------------------------------------

export async function archiveErrorBookItem(itemId: string, studentId: string): Promise<void> {
  await db
    .update(errorBookItems)
    .set({ status: "archived", updatedAt: new Date() })
    .where(
      and(
        eq(errorBookItems.id, itemId),
        eq(errorBookItems.studentId, studentId)
      )
    )
}

// ---------------------------------------------------------------------------
// 自动采集函数已迁移至 data-access-collection.ts
// 通过跨模块接口（exams/homework/questions data-access）避免直查多表，修复三层架构违规
// ---------------------------------------------------------------------------

export {
  collectFromExamSubmission,
  collectFromHomeworkSubmission,
} from "./data-access-collection"

// ---------------------------------------------------------------------------
// 共享工具函数：供 data-access-analytics.ts 使用
// ---------------------------------------------------------------------------

/** 构建学生错题查询的 where 条件（支持按学科过滤） */
export function buildStudentErrorWhereClause(
  studentIds: string[],
  subjectId?: string | null
): SQL | undefined {
  const conditions: SQL[] = [inArray(errorBookItems.studentId, studentIds)]
  if (subjectId) {
    conditions.push(eq(errorBookItems.subjectId, subjectId))
  }
  return and(...conditions)
}
