import "server-only"

import { cache } from "react"
import { and, asc, eq, inArray } from "drizzle-orm"

import { db } from "@/shared/db"
import { gradeRecords } from "@/shared/db/schema"
import {
  getClassesByGradeId,
  getClassNameById,
} from "@/modules/classes/data-access"
import { getSubjectOptions } from "@/modules/school/data-access"
import type { DataScope } from "@/shared/types/permissions"

import { buildScopeClassFilter, normalize, toNumber } from "./lib/grade-utils"
import {
  buildGradeTrendPoints,
  computeClassComparisonStats,
  computeGradeDistribution,
  computeSubjectComparisonStats,
  computeTrendAverage,
} from "./stats-service"
import type {
  ClassComparisonItem,
  GradeDistributionResult,
  GradeTrendResult,
  SubjectComparisonItem,
} from "./types"

export interface GradeTrendParams {
  classId: string
  subjectId?: string
  studentId?: string
  semester?: "1" | "2"
  scope: DataScope
  currentUserId?: string
}

export const getGradeTrend = cache(
  async (params: GradeTrendParams): Promise<GradeTrendResult | null> => {
    const conditions = [eq(gradeRecords.classId, params.classId)]
    if (params.subjectId) conditions.push(eq(gradeRecords.subjectId, params.subjectId))
    if (params.studentId) conditions.push(eq(gradeRecords.studentId, params.studentId))
    if (params.semester) conditions.push(eq(gradeRecords.semester, params.semester))

    if (params.scope.type === "class_members" && params.currentUserId) {
      conditions.push(eq(gradeRecords.studentId, params.currentUserId))
    }

    const scopeFilter = buildScopeClassFilter(params.scope)
    if (scopeFilter) conditions.push(scopeFilter)

    const rows = await db
      .select({
        record: gradeRecords,
      })
      .from(gradeRecords)
      .where(and(...conditions))
      .orderBy(asc(gradeRecords.createdAt))

    if (rows.length === 0) return null

    // Fetch display names via cross-module interfaces
    const className = await getClassNameById(params.classId)
    let subjectName = "All Subjects"
    if (params.subjectId) {
      const subjectOptions = await getSubjectOptions()
      const subject = subjectOptions.find((s) => s.id === params.subjectId)
      subjectName = subject?.name ?? "Unknown"
    }

    const points = buildGradeTrendPoints(rows)
    const avg = computeTrendAverage(points)
    const finalClassName = className ?? "Class"
    const studentLabel = params.studentId
      ? `Student ${params.studentId.slice(-4)}`
      : "Class Average"

    return {
      label: params.subjectId
        ? `${finalClassName} · ${subjectName} · ${studentLabel}`
        : `${finalClassName} · ${studentLabel}`,
      points,
      averageScore: avg,
    }
  }
)

export interface ClassComparisonParams {
  gradeId: string
  subjectId: string
  examId?: string
  scope: DataScope
}

export const getClassComparison = cache(
  async (params: ClassComparisonParams): Promise<ClassComparisonItem[]> => {
    const classRows = await getClassesByGradeId(params.gradeId)

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

    const scope = params.scope
    const scopeClassIdSet =
      scope.type === "class_taught" ? new Set(scope.classIds) : null
    const allowedClassRows = scopeClassIdSet
      ? classRows.filter((c) => scopeClassIdSet.has(c.id))
      : classRows

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

    const allowedClassIds = allowedClassRows.map((c) => c.id)

    const conditions = [
      inArray(gradeRecords.classId, allowedClassIds),
      eq(gradeRecords.subjectId, params.subjectId),
    ]
    if (params.examId) conditions.push(eq(gradeRecords.examId, params.examId))

    const allRows = await db
      .select({
        classId: gradeRecords.classId,
        score: gradeRecords.score,
        fullScore: gradeRecords.fullScore,
        studentId: gradeRecords.studentId,
      })
      .from(gradeRecords)
      .where(and(...conditions))

    const byClass = new Map<string, typeof allRows>()
    for (const r of allRows) {
      const existing = byClass.get(r.classId)
      if (existing) {
        existing.push(r)
      } else {
        byClass.set(r.classId, [r])
      }
    }

    const result: ClassComparisonItem[] = allowedClassRows.map((cls) => {
      const rows = byClass.get(cls.id) ?? []
      const stats = computeClassComparisonStats(rows)
      return {
        classId: cls.id,
        className: cls.name,
        ...stats,
      }
    })

    return result
  }
)

export interface SubjectComparisonParams {
  classId: string
  examId?: string
  scope: DataScope
}

export const getSubjectComparison = cache(
  async (params: SubjectComparisonParams): Promise<SubjectComparisonItem[]> => {
    const scopeFilter = buildScopeClassFilter(params.scope)
    const conditions = [eq(gradeRecords.classId, params.classId)]
    if (params.examId) conditions.push(eq(gradeRecords.examId, params.examId))
    if (scopeFilter) conditions.push(scopeFilter)

    const rows = await db
      .select({
        subjectId: gradeRecords.subjectId,
        score: gradeRecords.score,
        fullScore: gradeRecords.fullScore,
      })
      .from(gradeRecords)
      .where(and(...conditions))

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

    // Fetch subject names via cross-module interface
    const subjectOptions = await getSubjectOptions()
    const subjectNameById = new Map<string, string>()
    for (const s of subjectOptions) subjectNameById.set(s.id, s.name)

    const bySubject = new Map<string, { name: string; scores: number[] }>()

    for (const r of rows) {
      const sid = r.subjectId
      if (!sid) continue
      const entry = bySubject.get(sid) ?? { name: subjectNameById.get(sid) ?? "Unknown", scores: [] }
      entry.scores.push(normalize(toNumber(r.score), toNumber(r.fullScore)))
      bySubject.set(sid, entry)
    }

    const result: SubjectComparisonItem[] = []
    for (const [subjectId, entry] of bySubject.entries()) {
      if (entry.scores.length === 0) continue
      const stats = computeSubjectComparisonStats(entry.scores)
      result.push({
        subjectId,
        subjectName: entry.name,
        ...stats,
      })
    }

    return result.sort((a, b) => b.averageScore - a.averageScore)
  }
)

export interface GradeDistributionParams {
  classId: string
  subjectId?: string
  examId?: string
  scope: DataScope
  currentUserId?: string
}

export const getGradeDistribution = cache(
  async (params: GradeDistributionParams): Promise<GradeDistributionResult> => {
    const conditions = [eq(gradeRecords.classId, params.classId)]
    if (params.subjectId) conditions.push(eq(gradeRecords.subjectId, params.subjectId))
    if (params.examId) conditions.push(eq(gradeRecords.examId, params.examId))

    if (params.scope.type === "class_members" && params.currentUserId) {
      conditions.push(eq(gradeRecords.studentId, params.currentUserId))
    }

    const scopeFilter = buildScopeClassFilter(params.scope)
    if (scopeFilter) conditions.push(scopeFilter)

    const rows = await db
      .select({ score: gradeRecords.score, fullScore: gradeRecords.fullScore })
      .from(gradeRecords)
      .where(and(...conditions))

    return computeGradeDistribution(rows)
  }
)
