import "server-only"

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

import { getAuthContext } from "@/shared/lib/auth-guard"
import { db } from "@/shared/db"
import { loginLogs, roles, users, usersToRoles } from "@/shared/db/schema"
import { resolvePrimaryRole } from "@/shared/lib/role-utils"

export type UserProfile = {
  id: string
  name: string | null
  email: string
  image: string | null
  role: string | null
  phone: string | null
  address: string | null
  gender: string | null
  age: number | null
  onboardedAt: Date | null
  createdAt: Date
  updatedAt: Date
}

export const getUserProfile = cache(async (userId: string): Promise<UserProfile | null> => {
  const user = await db.query.users.findFirst({
    where: eq(users.id, userId),
  })

  if (!user) return null

  const roleRows = await db
    .select({ name: roles.name })
    .from(usersToRoles)
    .innerJoin(roles, eq(usersToRoles.roleId, roles.id))
    .where(eq(usersToRoles.userId, userId))
  const role = resolvePrimaryRole(roleRows.map((r) => r.name))

  return {
    id: user.id,
    name: user.name,
    email: user.email,
    image: user.image,
    role,
    phone: user.phone,
    address: user.address,
    gender: user.gender,
    age: user.age,
    onboardedAt: user.onboardedAt,
    createdAt: user.createdAt,
    updatedAt: user.updatedAt,
  }
})

/** Input for updating a user's profile (self-service) */
export type UpdateUserProfileInput = {
  name?: string
  phone?: string
  address?: string
  gender?: string
  age?: number
}

/**
 * Update a user's profile by id (self-service fields only).
 * Returns the updated user profile or null if the user was not found.
 */
export async function updateUserProfileById(
  userId: string,
  data: UpdateUserProfileInput
): Promise<UserProfile | null> {
  const updateData: Partial<typeof users.$inferInsert> = {}

  if (data.name !== undefined) updateData.name = data.name
  // Convert empty strings to null for cleaner DB
  if (data.phone !== undefined) updateData.phone = data.phone || null
  if (data.address !== undefined) updateData.address = data.address || null
  if (data.gender !== undefined) updateData.gender = data.gender || null
  if (data.age !== undefined) updateData.age = data.age

  if (Object.keys(updateData).length === 0) {
    return await getUserProfile(userId)
  }

  await db
    .update(users)
    .set(updateData)
    .where(eq(users.id, userId))

  // Invalidate cache by re-querying (cache is per-request anyway)
  return await getUserProfile(userId)
}

/**
 * Update a user's avatar image URL.
 * Returns the updated user profile or null if the user was not found.
 */
export async function updateUserAvatar(
  userId: string,
  imageUrl: string | null
): Promise<UserProfile | null> {
  await db
    .update(users)
    .set({ image: imageUrl })
    .where(eq(users.id, userId))

  return await getUserProfile(userId)
}

export type UsersDashboardStats = {
  userCount: number
  activeSessionsCount: number
  userRoleCounts: Array<{ role: string; count: number }>
  recentUsers: Array<{
    id: string
    name: string | null
    email: string
    role: string | null
    createdAt: string
  }>
}

export const getUsersDashboardStats = cache(async (): Promise<UsersDashboardStats> => {
  // audit-P1-9：原查询 sessions 表（JWT 策略下无数据）。
  // 改为查询最近 24 小时内 signin 成功事件数作为"活跃会话"近似值。
  const oneDayAgo = new Date(Date.now() - 24 * 60 * 60 * 1000)

  const [userCountRow, activeSessionsRow, userRoleCountRows, recentUserRows] = await Promise.all([
    db.select({ value: count() }).from(users),
    db
      .select({ value: count() })
      .from(loginLogs)
      .where(
        and(
          eq(loginLogs.action, "signin"),
          eq(loginLogs.status, "success"),
          gte(loginLogs.createdAt, oneDayAgo),
        ),
      ),
    db
      .select({ role: roles.name, value: count() })
      .from(usersToRoles)
      .innerJoin(roles, eq(usersToRoles.roleId, roles.id))
      .groupBy(roles.name),
    db
      .select({
        id: users.id,
        name: users.name,
        email: users.email,
        createdAt: users.createdAt,
      })
      .from(users)
      .orderBy(desc(users.createdAt))
      .limit(8),
  ])

  const userCount = Number(userCountRow[0]?.value ?? 0)
  const activeSessionsCount = Number(activeSessionsRow[0]?.value ?? 0)

  const userRoleCounts = userRoleCountRows
    .map((r) => ({ role: r.role ?? "unknown", count: Number(r.value ?? 0) }))
    .sort((a, b) => b.count - a.count)

  const recentUserIds = recentUserRows.map((u) => u.id)
  const recentRoleRows = recentUserIds.length
    ? await db
        .select({
          userId: usersToRoles.userId,
          roleName: roles.name,
        })
        .from(usersToRoles)
        .innerJoin(roles, eq(usersToRoles.roleId, roles.id))
        .where(inArray(usersToRoles.userId, recentUserIds))
    : []

  const rolesByUserId = new Map<string, string[]>()
  for (const row of recentRoleRows) {
    const list = rolesByUserId.get(row.userId) ?? []
    list.push(row.roleName)
    rolesByUserId.set(row.userId, list)
  }

  const recentUsers = recentUserRows.map((u) => {
    const roleNames = rolesByUserId.get(u.id) ?? []
    return {
      id: u.id,
      name: u.name,
      email: u.email,
      role: resolvePrimaryRole(roleNames),
      createdAt: u.createdAt.toISOString(),
    }
  })

  return {
    userCount,
    activeSessionsCount,
    userRoleCounts,
    recentUsers,
  }
})

// ---------------------------------------------------------------------------
// Cross-module query interfaces — read-only access for other modules
// ---------------------------------------------------------------------------

export type UserNameOption = {
  id: string
  name: string | null
  email: string
}

export type TeacherOption = {
  id: string
  name: string | null
  email: string
}

/** Returns the user row if the user has the specified role, otherwise null. */
export const getUserWithRole = cache(
  async (userId: string, roleName: string): Promise<{ id: string; name: string | null; email: string } | null> => {
    const id = userId.trim()
    if (!id) return null

    const [row] = await db
      .select({ id: users.id, name: users.name, email: users.email })
      .from(users)
      .innerJoin(usersToRoles, eq(usersToRoles.userId, users.id))
      .innerJoin(roles, eq(usersToRoles.roleId, roles.id))
      .where(and(eq(users.id, id), eq(roles.name, roleName)))
      .limit(1)

    return row ?? null
  }
)

/**
 * Returns the current authenticated student user (id + name) by reading the
 * auth context and verifying the "student" role via JOIN users + usersToRoles + roles.
 * Returns null if not authenticated or the user does not have the student role.
 */
export const getCurrentStudentUser = cache(async (): Promise<{ id: string; name: string; gradeId: string | null } | null> => {
  let ctx
  try {
    ctx = await getAuthContext()
  } catch {
    return null
  }
  const userId = ctx.userId

  const student = await getUserWithRole(userId, "student")

  if (!student) return null

  // 查询学生的 gradeId（关联 grades 表）
  const [userRow] = await db
    .select({ gradeId: users.gradeId })
    .from(users)
    .where(eq(users.id, student.id))
    .limit(1)

  return { id: student.id, name: student.name || "Student", gradeId: userRow?.gradeId ?? null }
})

/** Returns a map of userId -> { name, email } for the given user ids. */
export const getUserNamesByIds = cache(
  async (ids: string[]): Promise<Map<string, UserNameOption>> => {
    const uniqueIds = Array.from(new Set(ids.filter((v): v is string => typeof v === "string" && v.length > 0)))
    const result = new Map<string, UserNameOption>()
    if (uniqueIds.length === 0) return result

    const rows = await db
      .select({ id: users.id, name: users.name, email: users.email })
      .from(users)
      .where(inArray(users.id, uniqueIds))

    for (const r of rows) {
      result.set(r.id, { id: r.id, name: r.name, email: r.email })
    }
    return result
  }
)

/** Returns teachers (users with the "teacher" role) for the given user ids. */
export const getTeachersByIds = cache(
  async (teacherIds: string[]): Promise<TeacherOption[]> => {
    const uniqueIds = Array.from(new Set(teacherIds.filter((v): v is string => typeof v === "string" && v.length > 0)))
    if (uniqueIds.length === 0) return []

    const rows = await db
      .select({ id: users.id, name: users.name, email: users.email })
      .from(users)
      .innerJoin(usersToRoles, eq(usersToRoles.userId, users.id))
      .innerJoin(roles, eq(usersToRoles.roleId, roles.id))
      .where(and(eq(roles.name, "teacher"), inArray(users.id, uniqueIds)))
      .groupBy(users.id, users.name, users.email)

    return rows.map((r) => ({ id: r.id, name: r.name, email: r.email }))
  }
)

export type UserBasicInfo = {
  id: string
  name: string | null
  email: string
  image: string | null
  gradeId: string | null
}

/** Returns basic user info (id, name, email, image, gradeId) for a single user. */
export const getUserBasicInfo = cache(
  async (userId: string): Promise<UserBasicInfo | null> => {
    const id = userId.trim()
    if (!id) return null

    const [row] = await db
      .select({
        id: users.id,
        name: users.name,
        email: users.email,
        image: users.image,
        gradeId: users.gradeId,
      })
      .from(users)
      .where(eq(users.id, id))
      .limit(1)

    return row ?? null
  }
)

/** Returns user IDs for all users with the given gradeId. */
export const getUserIdsByGradeId = cache(
  async (gradeId: string): Promise<string[]> => {
    const id = gradeId.trim()
    if (!id) return []

    const rows = await db
      .select({ id: users.id })
      .from(users)
      .where(eq(users.gradeId, id))

    return rows.map((r) => r.id)
  }
)

/** Returns all user IDs (used for school-wide announcement notifications). */
export const getAllUserIds = cache(async (): Promise<string[]> => {
  const rows = await db.select({ id: users.id }).from(users)
  return rows.map((r) => r.id)
})

export type AdminUserListItem = {
  id: string
  name: string | null
  email: string
  roles: string[]
  phone: string | null
  createdAt: Date
}

export type AdminUserListResult = {
  items: AdminUserListItem[]
  total: number
  page: number
  pageSize: number
  totalPages: number
}

export async function getAdminUsers(params: {
  page?: number
  pageSize?: number
  search?: string
  role?: string
}): Promise<AdminUserListResult> {
  const page = Math.max(1, params.page ?? 1)
  const pageSize = Math.min(100, Math.max(1, params.pageSize ?? 20))
  const offset = (page - 1) * pageSize

  const conditions = []
  if (params.search) {
    const search = `%${params.search}%`
    conditions.push(
      or(ilike(users.name, search), ilike(users.email, search))
    )
  }

  const whereClause = conditions.length > 0 ? and(...conditions) : undefined

  const [userRows, countRow] = await Promise.all([
    db
      .select()
      .from(users)
      .where(whereClause)
      .orderBy(desc(users.createdAt))
      .limit(pageSize)
      .offset(offset),
    db.select({ value: count() }).from(users).where(whereClause),
  ])

  const userIds = userRows.map((u) => u.id)
  const roleRows = userIds.length
    ? await db
        .select({ userId: usersToRoles.userId, roleName: roles.name })
        .from(usersToRoles)
        .innerJoin(roles, eq(usersToRoles.roleId, roles.id))
        .where(inArray(usersToRoles.userId, userIds))
    : []

  const rolesByUserId = new Map<string, string[]>()
  for (const row of roleRows) {
    const list = rolesByUserId.get(row.userId) ?? []
    list.push(row.roleName)
    rolesByUserId.set(row.userId, list)
  }

  const items = userRows.map((u) => ({
    id: u.id,
    name: u.name,
    email: u.email,
    roles: rolesByUserId.get(u.id) ?? [],
    phone: u.phone,
    createdAt: u.createdAt,
  }))

  const total = Number(countRow[0]?.value ?? 0)
  return {
    items,
    total,
    page,
    pageSize,
    totalPages: Math.ceil(total / pageSize),
  }
}

export async function getAdminUserRoles(): Promise<string[]> {
  const rows = await db.select({ name: roles.name }).from(roles)
  return rows.map((r) => r.name)
}

/**
 * Delete a user by id.
 *
 * Related rows (accounts, usersToRoles, passwordSecurity) are
 * cleaned up via DB-level ON DELETE CASCADE foreign keys.
 * (sessions table is deprecated under JWT strategy — see schema.ts)
 *
 * Safety: throws if the user is the last remaining admin role holder,
 * preventing accidental lockout.
 */
export async function deleteUserById(userId: string): Promise<void> {
  // Last-admin protection: count how many users have the admin role
  const adminRoleRow = await db
    .select({ id: roles.id })
    .from(roles)
    .where(eq(roles.name, "admin"))
    .limit(1)

  if (adminRoleRow.length > 0) {
    const adminRoleId = adminRoleRow[0].id
    const adminCountRow = await db
      .select({ value: count() })
      .from(usersToRoles)
      .where(eq(usersToRoles.roleId, adminRoleId))

    const adminCount = Number(adminCountRow[0]?.value ?? 0)
    if (adminCount <= 1) {
      // Check if the target user is an admin
      const targetIsAdmin = await db
        .select({ userId: usersToRoles.userId })
        .from(usersToRoles)
        .where(and(eq(usersToRoles.userId, userId), eq(usersToRoles.roleId, adminRoleId)))
        .limit(1)

      if (targetIsAdmin.length > 0) {
        throw new Error("Cannot delete the last admin user")
      }
    }
  }

  await db.delete(users).where(eq(users.id, userId))
}
