import { NextResponse } from "next/server"
import { eq, inArray } from "drizzle-orm"

import { auth } from "@/auth"
import { db } from "@/shared/db"
import { classes, classSubjectTeachers, roles, users, usersToRoles, subjects } from "@/shared/db/schema"
import { DEFAULT_CLASS_SUBJECTS, type ClassSubject } from "@/modules/classes/types"
import { enrollStudentByInvitationCode } from "@/modules/classes/data-access"

export const dynamic = "force-dynamic"

function parseCodes(input: string) {
  const raw = input
    .split(/[\s,，;；]+/g)
    .map((s) => s.trim())
    .filter(Boolean)
  return Array.from(new Set(raw))
}

function isRecord(v: unknown): v is Record<string, unknown> {
  return typeof v === "object" && v !== null
}

export async function POST(req: Request) {
  const session = await auth()
  const userId = String(session?.user?.id ?? "").trim()
  if (!userId) return NextResponse.json({ success: false, message: "Unauthorized" }, { status: 401 })

  const body = await req.json().catch(() => null)
  if (!isRecord(body)) return NextResponse.json({ success: false, message: "Invalid payload" }, { status: 400 })

  const roleRaw = String(body.role ?? "").trim()
  const allowedRoles = ["student", "teacher", "parent", "admin"] as const
  const role = (allowedRoles as readonly string[]).includes(roleRaw) ? roleRaw : null
  if (!role) return NextResponse.json({ success: false, message: "Invalid role" }, { status: 400 })

  const currentRoleRows = await db
    .select({ name: roles.name })
    .from(usersToRoles)
    .innerJoin(roles, eq(usersToRoles.roleId, roles.id))
    .where(eq(usersToRoles.userId, userId))
  const currentMapped = currentRoleRows.map((r) => String(r.name ?? "").trim().toLowerCase())

  if (role === "admin" && !currentMapped.includes("admin")) {
    return NextResponse.json({ success: false, message: "Forbidden" }, { status: 403 })
  }

  const name = String(body.name ?? "").trim()
  if (!name) return NextResponse.json({ success: false, message: "Name is required" }, { status: 400 })

  const phone = String(body.phone ?? "").trim()
  const address = String(body.address ?? "").trim()

  const classCodesText = String(body.classCodes ?? "").trim()
  const codes = classCodesText.length ? parseCodes(classCodesText) : []

  const teacherSubjectsRaw = Array.isArray(body.teacherSubjects) ? body.teacherSubjects : []
  const teacherSubjects = teacherSubjectsRaw
    .map((s) => String(s).trim())
    .filter((s): s is ClassSubject => DEFAULT_CLASS_SUBJECTS.includes(s as ClassSubject))

  const roleRow = await db.query.roles.findFirst({
    where: eq(roles.name, role),
    columns: { id: true },
  })
  if (!roleRow) {
    await db.insert(roles).values({ name: role })
  }
  const resolvedRole = roleRow
    ?? (await db.query.roles.findFirst({ where: eq(roles.name, role), columns: { id: true } }))
  const roleId = resolvedRole?.id

  await db
    .update(users)
    .set({
      name,
      phone: phone.length ? phone : null,
      address: address.length ? address : null,
    })
    .where(eq(users.id, userId))

  if (roleId) {
    await db
      .insert(usersToRoles)
      .values({ userId, roleId })
      .onDuplicateKeyUpdate({ set: { roleId } })
  }

  if (role === "student" && codes.length) {
    for (const code of codes) {
      await enrollStudentByInvitationCode(userId, code)
    }
  }

  if (role === "teacher" && codes.length && teacherSubjects.length) {
    const classRows = await db
      .select({ id: classes.id, invitationCode: classes.invitationCode })
      .from(classes)
      .where(inArray(classes.invitationCode, codes))

    const byCode = new Map<string, string>()
    for (const r of classRows) {
      if (typeof r.invitationCode === "string") {
        byCode.set(r.invitationCode, r.id)
      }
    }

    // Resolve subject ids when possible (by name exact match)
    const subjectsFound = await db
      .select({ id: subjects.id, name: subjects.name })
      .from(subjects)
      .where(inArray(subjects.name, teacherSubjects))
    const subjectIdByName = new Map<string, string>()
    for (const s of subjectsFound) {
      if (s.name && s.id) subjectIdByName.set(String(s.name), String(s.id))
    }

    for (const code of codes) {
      const classId = byCode.get(code)
      if (!classId) continue
      for (const subject of teacherSubjects) {
        const subjectId = subjectIdByName.get(subject)
        if (!subjectId) continue
        await db
          .insert(classSubjectTeachers)
          .values({ classId, subjectId, teacherId: userId })
          .onDuplicateKeyUpdate({ set: { teacherId: userId, subjectId, updatedAt: new Date() } })
      }
    }
  }

  await db
    .update(users)
    .set({ onboardedAt: new Date() })
    .where(eq(users.id, userId))

  return NextResponse.json({ success: true })
}
