import "server-only"

import { cache } from "react"
import { and, count, desc, eq, inArray, or, sql } from "drizzle-orm"

import { db } from "@/shared/db"
import {
  homeworkAssignmentQuestions,
  homeworkAssignmentTargets,
  homeworkAssignments,
  homeworkSubmissions,
} from "@/shared/db/schema"
import {
  getActiveStudentIdsByClassId,
  getGradeIdsByClassIds,
  getStudentActiveClassId,
} from "@/modules/classes/data-access"
import { getExamIdsByGradeIds } from "@/modules/exams/data-access"
import { getUserNamesByIds } from "@/modules/users/data-access"

import type {
  HomeworkAssignmentAnalytics,
  HomeworkAssignmentQuestionAnalytics,
  HomeworkAssignmentStatus,
  StudentDashboardGradeProps,
  StudentHomeworkScoreAnalytics,
  StudentRanking,
  TeacherGradeTrendItem,
} from "./types"
import type { DataScope } from "@/shared/types/permissions"
import { getAssignmentMaxScoreById, toQuestionContent } from "./data-access"
import { isRecord } from "@/shared/lib/type-guards"

const isHomeworkAssignmentStatus = (v: unknown): v is HomeworkAssignmentStatus =>
  v === "draft" || v === "published" || v === "archived"

const toHomeworkAssignmentStatus = (v: string | null | undefined): HomeworkAssignmentStatus =>
  isHomeworkAssignmentStatus(v) ? v : "draft"

/**
 * Get grade trend data for a teacher's recent assignments.
 * Used by the teacher dashboard to visualize class performance over time.
 */
export const getTeacherGradeTrends = cache(async (teacherId: string, limit: number = 5): Promise<TeacherGradeTrendItem[]> => {
  const recentAssignments = await db.query.homeworkAssignments.findMany({
    where: and(
      eq(homeworkAssignments.creatorId, teacherId),
      or(eq(homeworkAssignments.status, "published"), eq(homeworkAssignments.status, "archived"))
    ),
    orderBy: [desc(homeworkAssignments.createdAt)],
    limit: limit,
  })

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

  const assignmentIds = recentAssignments.map((a) => a.id)

  const [maxScoreMap, targetCountRows, submissionStats] = await Promise.all([
    getAssignmentMaxScoreById(assignmentIds),
    db
      .select({
        assignmentId: homeworkAssignmentTargets.assignmentId,
        count: count(homeworkAssignmentTargets.studentId),
      })
      .from(homeworkAssignmentTargets)
      .where(inArray(homeworkAssignmentTargets.assignmentId, assignmentIds))
      .groupBy(homeworkAssignmentTargets.assignmentId),
    db
      .select({
        assignmentId: homeworkSubmissions.assignmentId,
        avgScore: sql<number>`AVG(${homeworkSubmissions.score})`,
        count: count(homeworkSubmissions.id),
      })
      .from(homeworkSubmissions)
      .where(
        and(
          inArray(homeworkSubmissions.assignmentId, assignmentIds),
          eq(homeworkSubmissions.status, "graded")
        )
      )
      .groupBy(homeworkSubmissions.assignmentId),
  ])

  const targetCountMap = new Map<string, number>()
  for (const r of targetCountRows) targetCountMap.set(r.assignmentId, r.count)

  const statsMap = new Map<string, { avg: number; count: number }>()
  for (const r of submissionStats) statsMap.set(r.assignmentId, { avg: Number(r.avgScore), count: Number(r.count) })

  return recentAssignments.map((a) => {
    const stats = statsMap.get(a.id) ?? { avg: 0, count: 0 }
    return {
      id: a.id,
      title: a.title,
      averageScore: stats.avg,
      maxScore: maxScoreMap.get(a.id) ?? 0,
      submissionCount: stats.count,
      totalStudents: targetCountMap.get(a.id) ?? 0,
      createdAt: a.createdAt.toISOString(),
    }
  }).reverse() // Reverse to show trend from left (older) to right (newer)
})

/**
 * Get detailed analytics for a specific homework assignment.
 * Includes per-question error rates and wrong answer samples.
 */
export const getHomeworkAssignmentAnalytics = cache(
  async (assignmentId: string): Promise<HomeworkAssignmentAnalytics | null> => {
    const assignment = await db.query.homeworkAssignments.findFirst({
      where: eq(homeworkAssignments.id, assignmentId),
      with: {
        sourceExam: true,
      },
    })

    if (!assignment) return null

    const [targetsRows, submissionsRows, submittedRows, gradedRows, assignmentQuestions] = await Promise.all([
      db
        .select({ c: count() })
        .from(homeworkAssignmentTargets)
        .where(eq(homeworkAssignmentTargets.assignmentId, assignmentId)),
      db
        .select({ c: count() })
        .from(homeworkSubmissions)
        .where(eq(homeworkSubmissions.assignmentId, assignmentId)),
      db
        .select({ c: sql<number>`COUNT(DISTINCT ${homeworkSubmissions.studentId})` })
        .from(homeworkSubmissions)
        .where(
          and(
            eq(homeworkSubmissions.assignmentId, assignmentId),
            inArray(homeworkSubmissions.status, ["submitted", "graded"])
          )
        ),
      db
        .select({ c: sql<number>`COUNT(DISTINCT ${homeworkSubmissions.studentId})` })
        .from(homeworkSubmissions)
        .where(and(eq(homeworkSubmissions.assignmentId, assignmentId), eq(homeworkSubmissions.status, "graded"))),
      db.query.homeworkAssignmentQuestions.findMany({
        where: eq(homeworkAssignmentQuestions.assignmentId, assignmentId),
        with: { question: true },
        orderBy: (q, { asc }) => [asc(q.order)],
      }),
    ])

    const statsByQuestionId = new Map<string, HomeworkAssignmentQuestionAnalytics>()

    for (const aq of assignmentQuestions) {
      statsByQuestionId.set(aq.questionId, {
        questionId: aq.questionId,
        questionType: aq.question.type,
        questionContent: toQuestionContent(aq.question.content),
        maxScore: aq.score ?? 0,
        order: aq.order ?? 0,
        errorCount: 0,
        errorRate: 0,
      })
    }

    const gradedSubmissionsAll = await db.query.homeworkSubmissions.findMany({
      where: and(
        eq(homeworkSubmissions.assignmentId, assignmentId),
        eq(homeworkSubmissions.status, "graded")
      ),
      orderBy: (s, { desc }) => [desc(s.updatedAt)],
      with: {
        answers: true,
        student: true,
      },
    })

    const latestByStudentId = new Map<string, (typeof gradedSubmissionsAll)[number]>()
    for (const s of gradedSubmissionsAll) {
      if (!latestByStudentId.has(s.studentId)) latestByStudentId.set(s.studentId, s)
    }
    const gradedSubmissions = Array.from(latestByStudentId.values())

    const scoreBySubmissionQuestion = new Map<string, number>()
    const answerBySubmissionQuestion = new Map<string, unknown>()
    for (const sub of gradedSubmissions) {
      for (const ans of sub.answers) {
        const key = `${sub.id}|${ans.questionId}`
        if (scoreBySubmissionQuestion.has(key)) continue
        scoreBySubmissionQuestion.set(key, ans.score ?? 0)
        const raw = ans.answerContent
        if (isRecord(raw) && "answer" in raw) {
          answerBySubmissionQuestion.set(key, raw.answer)
        } else {
          answerBySubmissionQuestion.set(key, raw)
        }
      }
    }

    const denom = gradedSubmissions.length
    if (denom > 0) {
      for (const q of statsByQuestionId.values()) {
        if (q.maxScore <= 0) continue
        let errors = 0
        const wrongAnswers: Array<{ studentId: string; studentName: string; answerContent: unknown }> = []
        for (const sub of gradedSubmissions) {
          const key = `${sub.id}|${q.questionId}`
          const score = scoreBySubmissionQuestion.get(key) ?? 0
          if (score < q.maxScore) {
            errors += 1
            wrongAnswers.push({
              studentId: sub.studentId,
              studentName: sub.student.name || "Unknown",
              answerContent: answerBySubmissionQuestion.get(key),
            })
          }
        }
        q.errorCount = errors
        q.errorRate = errors / denom
        q.wrongAnswers = wrongAnswers.slice(0, 500)
      }
    }

    const questions: HomeworkAssignmentQuestionAnalytics[] = Array.from(statsByQuestionId.values())
      .sort((a, b) => a.order - b.order)

    const analytics: HomeworkAssignmentAnalytics = {
      assignment: {
        id: assignment.id,
        title: assignment.title,
        description: assignment.description,
        status: toHomeworkAssignmentStatus(assignment.status),
        sourceExamId: assignment.sourceExamId,
        sourceExamTitle: assignment.sourceExam?.title ?? null,
        structure: assignment.structure as unknown,
        availableAt: assignment.availableAt ? assignment.availableAt.toISOString() : null,
        dueAt: assignment.dueAt ? assignment.dueAt.toISOString() : null,
        allowLate: assignment.allowLate,
        lateDueAt: assignment.lateDueAt ? assignment.lateDueAt.toISOString() : null,
        maxAttempts: assignment.maxAttempts,
        targetCount: targetsRows[0]?.c ?? 0,
        submissionCount: submissionsRows[0]?.c ?? 0,
        submittedCount: submittedRows[0]?.c ?? 0,
        gradedCount: gradedRows[0]?.c ?? 0,
        createdAt: assignment.createdAt.toISOString(),
        updatedAt: assignment.updatedAt.toISOString(),
      },
      gradedSampleCount: gradedSubmissions.length,
      questions,
    }

    return analytics
  }
)

/**
 * Get student dashboard grade data including trend, recent scores, and class ranking.
 * The ranking calculation queries all classmates' graded submissions and computes
 * relative position by total percentage score.
 */
export const getStudentDashboardGrades = cache(async (studentId: string): Promise<StudentDashboardGradeProps> => {
  const id = studentId.trim()
  if (!id) return { trend: [], recent: [], ranking: null }

  const targetAssignmentIdsRows = await db
    .select({ assignmentId: homeworkAssignmentTargets.assignmentId })
    .from(homeworkAssignmentTargets)
    .where(eq(homeworkAssignmentTargets.studentId, id))

  const targetAssignmentIds = Array.from(new Set(targetAssignmentIdsRows.map((r) => r.assignmentId)))
  if (targetAssignmentIds.length === 0) return { trend: [], recent: [], ranking: null }

  const gradedSubmissions = await db.query.homeworkSubmissions.findMany({
    where: and(
      eq(homeworkSubmissions.studentId, id),
      inArray(homeworkSubmissions.assignmentId, targetAssignmentIds),
      eq(homeworkSubmissions.status, "graded")
    ),
    orderBy: (s, { desc }) => [desc(s.updatedAt)],
    limit: 200,
  })

  const latestByAssignmentId = new Map<string, (typeof gradedSubmissions)[number]>()
  for (const s of gradedSubmissions) {
    if (!latestByAssignmentId.has(s.assignmentId)) latestByAssignmentId.set(s.assignmentId, s)
  }

  const unique = Array.from(latestByAssignmentId.values()).sort((a, b) => {
    const aTime = (a.submittedAt ?? a.updatedAt).getTime()
    const bTime = (b.submittedAt ?? b.updatedAt).getTime()
    return aTime - bTime
  })

  const trendSubmissions = unique.slice(-10)
  const recentSubmissions = [...trendSubmissions].sort((a, b) => {
    const aTime = (a.submittedAt ?? a.updatedAt).getTime()
    const bTime = (b.submittedAt ?? b.updatedAt).getTime()
    return bTime - aTime
  })

  const assignmentIds = Array.from(new Set(trendSubmissions.map((s) => s.assignmentId)))
  const assignments = await db.query.homeworkAssignments.findMany({
    where: inArray(homeworkAssignments.id, assignmentIds),
  })
  const titleByAssignmentId = new Map(assignments.map((a) => [a.id, a.title] as const))
  const maxScoreByAssignmentId = await getAssignmentMaxScoreById(assignmentIds)

  const toAnalytics = (s: (typeof trendSubmissions)[number]): StudentHomeworkScoreAnalytics => {
    const maxScore = maxScoreByAssignmentId.get(s.assignmentId) ?? 0
    const score = s.score ?? 0
    const percentage = maxScore > 0 ? (score / maxScore) * 100 : 0
    return {
      assignmentId: s.assignmentId,
      assignmentTitle: titleByAssignmentId.get(s.assignmentId) ?? "Untitled",
      score,
      maxScore,
      percentage,
      submittedAt: (s.submittedAt ?? s.updatedAt).toISOString(),
    }
  }

  const trend = trendSubmissions.map(toAnalytics)
  const recent = recentSubmissions.map(toAnalytics).slice(0, 5)

  const classId = await getStudentActiveClassId(id)
  if (!classId) return { trend, recent, ranking: null }

  const classStudentIds = await getActiveStudentIdsByClassId(classId)
  const classSize = classStudentIds.length
  if (classSize === 0) return { trend, recent, ranking: null }

  const classAssignmentIdsRows = await db
    .selectDistinct({ assignmentId: homeworkAssignmentTargets.assignmentId })
    .from(homeworkAssignmentTargets)
    .where(inArray(homeworkAssignmentTargets.studentId, classStudentIds))

  const classAssignmentIds = Array.from(new Set(classAssignmentIdsRows.map((r) => r.assignmentId)))
  if (classAssignmentIds.length === 0) return { trend, recent, ranking: null }

  const classMaxScoreByAssignmentId = await getAssignmentMaxScoreById(classAssignmentIds)

  const classGradedSubmissions = await db.query.homeworkSubmissions.findMany({
    where: and(
      inArray(homeworkSubmissions.studentId, classStudentIds),
      inArray(homeworkSubmissions.assignmentId, classAssignmentIds),
      eq(homeworkSubmissions.status, "graded")
    ),
    orderBy: (s, { desc }) => [desc(s.updatedAt)],
    limit: 5000,
  })

  const latestByStudentAssignment = new Map<string, (typeof classGradedSubmissions)[number]>()
  for (const s of classGradedSubmissions) {
    const key = `${s.studentId}|${s.assignmentId}`
    if (!latestByStudentAssignment.has(key)) latestByStudentAssignment.set(key, s)
  }

  const totalsByStudentId = new Map<string, { score: number; maxScore: number }>()
  for (const sub of latestByStudentAssignment.values()) {
    const maxScore = classMaxScoreByAssignmentId.get(sub.assignmentId) ?? 0
    const score = sub.score ?? 0
    const prev = totalsByStudentId.get(sub.studentId) ?? { score: 0, maxScore: 0 }
    totalsByStudentId.set(sub.studentId, {
      score: prev.score + score,
      maxScore: prev.maxScore + maxScore,
    })
  }

  const userNamesMap = await getUserNamesByIds(classStudentIds)
  const myName = userNamesMap.get(id)?.name ?? "Student"

  const ranked = classStudentIds
    .map((studentId) => {
      const totals = totalsByStudentId.get(studentId) ?? { score: 0, maxScore: 0 }
      const percentage = totals.maxScore > 0 ? (totals.score / totals.maxScore) * 100 : 0
      return { studentId, percentage, totals }
    })
    .sort((a, b) => {
      if (b.percentage !== a.percentage) return b.percentage - a.percentage
      return a.studentId.localeCompare(b.studentId)
    })

  const myIndex = ranked.findIndex((r) => r.studentId === id)
  if (myIndex < 0) return { trend, recent, ranking: null }

  const myTotals = ranked[myIndex]?.totals ?? { score: 0, maxScore: 0 }
  const myPercentage = myTotals.maxScore > 0 ? (myTotals.score / myTotals.maxScore) * 100 : 0

  const ranking: StudentRanking = {
    studentId: id,
    studentName: myName,
    rank: myIndex + 1,
    classSize,
    totalScore: myTotals.score,
    totalMaxScore: myTotals.maxScore,
    percentage: myPercentage,
  }

  return { trend, recent, ranking }
})

export type HomeworkDashboardStats = {
  homeworkAssignmentCount: number
  homeworkAssignmentPublishedCount: number
  homeworkSubmissionCount: number
  homeworkSubmissionToGradeCount: number
}

export const getHomeworkDashboardStats = cache(async (scope?: DataScope): Promise<HomeworkDashboardStats> => {
  const homeworkConditions = []
  const submissionConditions = []

  if (scope && scope.type !== "all") {
    if (scope.type === "owned") {
      homeworkConditions.push(eq(homeworkAssignments.creatorId, scope.userId))
      const ownedAssignmentIds = db
        .select({ id: homeworkAssignments.id })
        .from(homeworkAssignments)
        .where(eq(homeworkAssignments.creatorId, scope.userId))
      submissionConditions.push(inArray(homeworkSubmissions.assignmentId, ownedAssignmentIds))
    }
    if (scope.type === "grade_managed" && scope.gradeIds.length > 0) {
      const gradeExamIds = await getExamIdsByGradeIds(scope.gradeIds)
      homeworkConditions.push(inArray(homeworkAssignments.sourceExamId, gradeExamIds))
      const gradeAssignmentIds = db
        .select({ id: homeworkAssignments.id })
        .from(homeworkAssignments)
        .where(inArray(homeworkAssignments.sourceExamId, gradeExamIds))
      submissionConditions.push(inArray(homeworkSubmissions.assignmentId, gradeAssignmentIds))
    }
    if (scope.type === "class_taught" && scope.classIds.length > 0) {
      const gradeIds = await getGradeIdsByClassIds(scope.classIds)
      if (gradeIds.length > 0) {
        const gradeExamIds = await getExamIdsByGradeIds(gradeIds)
        homeworkConditions.push(inArray(homeworkAssignments.sourceExamId, gradeExamIds))
        const gradeAssignmentIds = db
          .select({ id: homeworkAssignments.id })
          .from(homeworkAssignments)
          .where(inArray(homeworkAssignments.sourceExamId, gradeExamIds))
        submissionConditions.push(inArray(homeworkSubmissions.assignmentId, gradeAssignmentIds))
      }
    }
  }

  const [
    homeworkAssignmentCountRow,
    homeworkAssignmentPublishedCountRow,
    homeworkSubmissionCountRow,
    homeworkSubmissionToGradeCountRow,
  ] = await Promise.all([
    db.select({ value: count() }).from(homeworkAssignments).where(homeworkConditions.length ? and(...homeworkConditions) : undefined),
    db.select({ value: count() }).from(homeworkAssignments).where(
      homeworkConditions.length
        ? and(eq(homeworkAssignments.status, "published"), ...homeworkConditions)
        : eq(homeworkAssignments.status, "published")
    ),
    db.select({ value: count() }).from(homeworkSubmissions).where(submissionConditions.length ? and(...submissionConditions) : undefined),
    db.select({ value: count() }).from(homeworkSubmissions).where(
      submissionConditions.length
        ? and(eq(homeworkSubmissions.status, "submitted"), ...submissionConditions)
        : eq(homeworkSubmissions.status, "submitted")
    ),
  ])

  return {
    homeworkAssignmentCount: Number(homeworkAssignmentCountRow[0]?.value ?? 0),
    homeworkAssignmentPublishedCount: Number(homeworkAssignmentPublishedCountRow[0]?.value ?? 0),
    homeworkSubmissionCount: Number(homeworkSubmissionCountRow[0]?.value ?? 0),
    homeworkSubmissionToGradeCount: Number(homeworkSubmissionToGradeCountRow[0]?.value ?? 0),
  }
})
