import { describe, it, expect } from "vitest"

import {
  toWeekday,
  countStudentAssignments,
  sortUpcomingAssignments,
  filterTodaySchedule,
  computeTeacherMetrics,
  getGreetingKey,
  type Weekday,
} from "@/modules/dashboard/lib/dashboard-utils"
import type { StudentHomeworkAssignmentListItem } from "@/modules/homework/types"
import type { ClassScheduleItem, TeacherClass } from "@/modules/classes/types"
import type {
  HomeworkAssignmentListItem,
  HomeworkSubmissionListItem,
  TeacherGradeTrendItem,
} from "@/modules/homework/types"

describe("toWeekday", () => {
  it("returns 1 for Monday", () => {
    const monday = new Date("2026-06-22T00:00:00") // 2026-06-22 is Monday
    expect(toWeekday(monday)).toBe(1 as Weekday)
  })

  it("returns 7 for Sunday", () => {
    const sunday = new Date("2026-06-28T00:00:00") // 2026-06-28 is Sunday
    expect(toWeekday(sunday)).toBe(7 as Weekday)
  })

  it("returns 6 for Saturday", () => {
    const saturday = new Date("2026-06-27T00:00:00") // 2026-06-27 is Saturday
    expect(toWeekday(saturday)).toBe(6 as Weekday)
  })

  it("returns 2 for Tuesday", () => {
    const tuesday = new Date("2026-06-23T00:00:00") // 2026-06-23 is Tuesday
    expect(toWeekday(tuesday)).toBe(2 as Weekday)
  })
})

describe("getGreetingKey", () => {
  it("returns morning before 12:00", () => {
    expect(getGreetingKey(new Date("2026-06-22T08:00:00"))).toBe("morning")
    expect(getGreetingKey(new Date("2026-06-22T11:59:59"))).toBe("morning")
  })

  it("returns afternoon between 12:00 and 18:00", () => {
    expect(getGreetingKey(new Date("2026-06-22T12:00:00"))).toBe("afternoon")
    expect(getGreetingKey(new Date("2026-06-22T17:59:59"))).toBe("afternoon")
  })

  it("returns evening at 18:00 or later", () => {
    expect(getGreetingKey(new Date("2026-06-22T18:00:00"))).toBe("evening")
    expect(getGreetingKey(new Date("2026-06-22T23:59:59"))).toBe("evening")
  })
})

describe("countStudentAssignments", () => {
  const now = new Date("2026-06-22T10:00:00")

  it("returns zero counts for empty list", () => {
    expect(countStudentAssignments([], now)).toEqual({
      dueSoonCount: 0,
      overdueCount: 0,
      gradedCount: 0,
    })
  })

  it("counts graded assignments", () => {
    const assignments = [
      makeAssignment("1", "graded", null),
      makeAssignment("2", "graded", null),
    ]
    expect(countStudentAssignments(assignments, now)).toEqual({
      dueSoonCount: 0,
      overdueCount: 0,
      gradedCount: 2,
    })
  })

  it("counts due soon (within 7 days)", () => {
    const in3Days = new Date(now)
    in3Days.setDate(in3Days.getDate() + 3)
    const assignments = [
      makeAssignment("1", "in_progress", in3Days.toISOString()),
    ]
    expect(countStudentAssignments(assignments, now).dueSoonCount).toBe(1)
  })

  it("counts overdue", () => {
    const past = new Date(now)
    past.setDate(past.getDate() - 1)
    const assignments = [
      makeAssignment("1", "submitted", past.toISOString()),
    ]
    expect(countStudentAssignments(assignments, now).overdueCount).toBe(1)
  })

  it("ignores assignments without dueAt (non-graded)", () => {
    const assignments = [
      makeAssignment("1", "in_progress", null),
    ]
    expect(countStudentAssignments(assignments, now)).toEqual({
      dueSoonCount: 0,
      overdueCount: 0,
      gradedCount: 0,
    })
  })

  it("respects custom dueSoonWindowDays", () => {
    const in15Days = new Date(now)
    in15Days.setDate(in15Days.getDate() + 15)
    const assignments = [
      makeAssignment("1", "in_progress", in15Days.toISOString()),
    ]
    // default 7-day window: not due soon
    expect(countStudentAssignments(assignments, now).dueSoonCount).toBe(0)
    // 30-day window: due soon
    expect(countStudentAssignments(assignments, now, 30).dueSoonCount).toBe(1)
  })
})

describe("sortUpcomingAssignments", () => {
  it("returns empty array for empty input", () => {
    expect(sortUpcomingAssignments([])).toEqual([])
  })

  it("sorts by dueAt ascending", () => {
    const assignments = [
      makeAssignment("1", "in_progress", "2026-06-25T00:00:00"),
      makeAssignment("2", "in_progress", "2026-06-23T00:00:00"),
      makeAssignment("3", "in_progress", "2026-06-24T00:00:00"),
    ]
    const sorted = sortUpcomingAssignments(assignments)
    expect(sorted.map((a) => a.id)).toEqual(["2", "3", "1"])
  })

  it("puts assignments without dueAt at the end", () => {
    const assignments = [
      makeAssignment("1", "in_progress", null),
      makeAssignment("2", "in_progress", "2026-06-23T00:00:00"),
    ]
    const sorted = sortUpcomingAssignments(assignments)
    expect(sorted.map((a) => a.id)).toEqual(["2", "1"])
  })

  it("respects limit parameter", () => {
    const assignments = Array.from({ length: 10 }, (_, i) =>
      makeAssignment(`${i}`, "in_progress", `2026-06-${23 + i}T00:00:00`),
    )
    expect(sortUpcomingAssignments(assignments, 3)).toHaveLength(3)
  })

  it("does not mutate original array", () => {
    const assignments = [
      makeAssignment("1", "in_progress", "2026-06-25T00:00:00"),
      makeAssignment("2", "in_progress", "2026-06-23T00:00:00"),
    ]
    sortUpcomingAssignments(assignments)
    expect(assignments.map((a) => a.id)).toEqual(["1", "2"])
  })
})

describe("filterTodaySchedule", () => {
  const weekday = 1 as Weekday // Monday

  it("returns empty array for empty schedule", () => {
    expect(filterTodaySchedule([], weekday)).toEqual([])
  })

  it("filters by weekday", () => {
    const schedule: ClassScheduleItem[] = [
      makeScheduleItem("1", 1, "08:00"),
      makeScheduleItem("2", 2, "09:00"),
      makeScheduleItem("3", 1, "07:00"),
    ]
    const result = filterTodaySchedule(schedule, weekday)
    expect(result).toHaveLength(2)
    expect((result as Array<{ id: string }>).map((s) => s.id)).toEqual(["3", "1"])
  })

  it("sorts by startTime ascending", () => {
    const schedule: ClassScheduleItem[] = [
      makeScheduleItem("1", 1, "10:00"),
      makeScheduleItem("2", 1, "08:00"),
      makeScheduleItem("3", 1, "09:00"),
    ]
    const result = filterTodaySchedule(schedule, weekday) as Array<{ id: string }>
    expect(result.map((s) => s.id)).toEqual(["2", "3", "1"])
  })

  it("uses classNameById map when provided", () => {
    const schedule: ClassScheduleItem[] = [
      makeScheduleItem("1", 1, "08:00"),
    ]
    const classNameById = new Map([["class-1", "Math 101"]])
    const result = filterTodaySchedule(schedule, weekday, classNameById) as Array<{ className: string }>
    expect(result[0].className).toBe("Math 101")
  })

  it("falls back to 'Class' when classNameById missing", () => {
    const schedule: ClassScheduleItem[] = [
      makeScheduleItem("1", 1, "08:00"),
    ]
    const result = filterTodaySchedule(schedule, weekday) as Array<{ className: string }>
    expect(result[0].className).toBe("Class")
  })
})

describe("computeTeacherMetrics", () => {
  const now = new Date("2026-06-22T10:00:00") // Monday

  it("returns zero metrics for empty inputs", () => {
    const metrics = computeTeacherMetrics([], [], [], [], [], now)
    expect(metrics.toGradeCount).toBe(0)
    expect(metrics.activeAssignmentsCount).toBe(0)
    expect(metrics.averageScore).toBe(0)
    expect(metrics.submissionRate).toBe(0)
    expect(metrics.todayScheduleItems).toEqual([])
    expect(metrics.submissionsToGrade).toEqual([])
  })

  it("counts toGrade from submitted (non-graded) submissions", () => {
    const submissions: HomeworkSubmissionListItem[] = [
      makeSubmission("1", "submitted"),
      makeSubmission("2", "submitted"),
      makeSubmission("3", "graded"),
    ]
    const metrics = computeTeacherMetrics([], [], [], submissions, [], now)
    expect(metrics.toGradeCount).toBe(2)
  })

  it("counts active assignments (published)", () => {
    const assignments: HomeworkAssignmentListItem[] = [
      makeAssignmentTeacher("1", "published"),
      makeAssignmentTeacher("2", "published"),
      makeAssignmentTeacher("3", "draft"),
    ]
    const metrics = computeTeacherMetrics([], [], assignments, [], [], now)
    expect(metrics.activeAssignmentsCount).toBe(2)
  })

  it("computes averageScore from gradeTrends", () => {
    const trends: TeacherGradeTrendItem[] = [
      makeTrend("1", 80),
      makeTrend("2", 90),
    ]
    const metrics = computeTeacherMetrics([], [], [], [], trends, now)
    expect(metrics.averageScore).toBe(85)
  })

  it("computes submissionRate as percentage", () => {
    const trends: TeacherGradeTrendItem[] = [
      makeTrendWithSubmissions("1", 8, 10),
      makeTrendWithSubmissions("2", 6, 10),
    ]
    const metrics = computeTeacherMetrics([], [], [], [], trends, now)
    // (8 + 6) / (10 + 10) * 100 = 70
    expect(metrics.submissionRate).toBe(70)
  })

  it("returns submissionRate 0 when no students", () => {
    const trends: TeacherGradeTrendItem[] = [
      makeTrendWithSubmissions("1", 0, 0),
    ]
    const metrics = computeTeacherMetrics([], [], [], [], trends, now)
    expect(metrics.submissionRate).toBe(0)
  })

  it("filters today schedule by current weekday", () => {
    const classes: TeacherClass[] = [
      makeClass("c1", "Math 101"),
    ]
    const schedule: ClassScheduleItem[] = [
      makeScheduleItem("s1", 1, "08:00"), // Monday
      makeScheduleItem("s2", 2, "09:00"), // Tuesday
    ]
    const metrics = computeTeacherMetrics(classes, schedule, [], [], [], now)
    expect(metrics.todayScheduleItems).toHaveLength(1)
  })

  it("limits submissionsToGrade to 6 items", () => {
    const submissions: HomeworkSubmissionListItem[] = Array.from({ length: 10 }, (_, i) =>
      makeSubmission(`${i}`, "submitted"),
    )
    const metrics = computeTeacherMetrics([], [], [], submissions, [], now)
    expect(metrics.submissionsToGrade).toHaveLength(6)
  })
})

// ============ Helpers ============

function makeAssignment(
  id: string,
  progressStatus: string,
  dueAt: string | null,
): StudentHomeworkAssignmentListItem {
  return {
    id,
    title: `Assignment ${id}`,
    subjectName: null,
    dueAt,
    availableAt: null,
    maxAttempts: 1,
    attemptsUsed: 0,
    progressStatus: progressStatus as StudentHomeworkAssignmentListItem["progressStatus"],
    latestSubmissionId: null,
    latestSubmittedAt: null,
    latestScore: null,
  }
}

function makeScheduleItem(
  id: string,
  weekday: number,
  startTime: string,
): ClassScheduleItem {
  return {
    id,
    classId: `class-${id}`,
    weekday: weekday as ClassScheduleItem["weekday"],
    startTime,
    endTime: "09:00",
    course: `Course ${id}`,
    location: null,
  } as ClassScheduleItem
}

function makeSubmission(
  id: string,
  status: string,
): HomeworkSubmissionListItem {
  return {
    id,
    assignmentId: `assignment-${id}`,
    assignmentTitle: `Assignment ${id}`,
    studentName: `Student ${id}`,
    status: status as HomeworkSubmissionListItem["status"],
    submittedAt: "2026-06-22T10:00:00",
    isLate: false,
    score: null,
  }
}

function makeAssignmentTeacher(
  id: string,
  status: string,
): HomeworkAssignmentListItem {
  return {
    id,
    sourceExamId: null,
    sourceExamTitle: null,
    title: `Assignment ${id}`,
    status: status as HomeworkAssignmentListItem["status"],
    availableAt: null,
    dueAt: null,
    allowLate: false,
    lateDueAt: null,
    maxAttempts: 1,
    createdAt: "2026-06-01T00:00:00",
    updatedAt: "2026-06-01T00:00:00",
    targetCount: 0,
    submittedCount: 0,
    gradedCount: 0,
    averageScore: null,
    overdueCount: 0,
  }
}

function makeTrend(id: string, averageScore: number): TeacherGradeTrendItem {
  return {
    id,
    title: `Trend ${id}`,
    averageScore,
    maxScore: 100,
    submissionCount: 10,
    totalStudents: 10,
    createdAt: "2026-06-01T00:00:00",
  }
}

function makeTrendWithSubmissions(
  id: string,
  submissionCount: number,
  totalStudents: number,
): TeacherGradeTrendItem {
  return {
    id,
    title: `Trend ${id}`,
    averageScore: 80,
    maxScore: 100,
    submissionCount,
    totalStudents,
    createdAt: "2026-06-01T00:00:00",
  }
}

function makeClass(id: string, name: string): TeacherClass {
  return {
    id,
    name,
    grade: "G1",
    homeroom: null,
    room: null,
    studentCount: 0,
  } as TeacherClass
}
