import "server-only"

import { createId } from "@paralleldrive/cuid2"
import { eq } from "drizzle-orm"
import { hash } from "bcryptjs"

import { db } from "@/shared/db"
import { roles, users, usersToRoles } from "@/shared/db/schema"
import { normalizeBcryptHash } from "@/shared/lib/bcrypt-utils"

import { calcAge, isMinorByBirthDate } from "./schema"
import type { RegisterInput, RegisterResult } from "./types"

/** bcrypt cost factor（推荐 12，平衡性能与安全） */
const BCRYPT_COST = 12

/** 默认注册角色：仅学生可通过开放注册创建账号 */
const DEFAULT_REGISTER_ROLE = "student"

/**
 * 查询邮箱是否已被注册。
 *
 * 仅返回布尔值，不暴露用户 id 等敏感信息，避免邮箱枚举攻击。
 */
export async function isEmailAvailable(email: string): Promise<boolean> {
  const normalized = email.trim().toLowerCase()
  if (!normalized) return false
  const existing = await db.query.users.findFirst({
    where: eq(users.email, normalized),
    columns: { id: true },
  })
  return !existing
}

/**
 * 创建新用户（注册）。
 *
 * - 仅创建 users 与 users_to_roles（默认 student）记录，不写其他表。
 * - 角色查找失败时返回错误（不自动创建角色，避免污染 RBAC）。
 * - 密码使用 bcrypt cost=12 哈希并规范化。
 * - 写入 `consentAcceptedAt` 标记用户已同意隐私政策与用户协议。
 *
 * 安全设计：
 * - 不返回 password 字段。
 * - 不触发审计日志（由 actions 层调用 logLoginEvent）。
 * - 调用方应在外层添加速率限制与 Zod 校验。
 */
export async function createUser(
  input: RegisterInput
): Promise<RegisterResult> {
  const normalizedEmail = input.email.trim().toLowerCase()
  const hashedPassword = normalizeBcryptHash(await hash(input.password, BCRYPT_COST))

  const userId = createId()
  await db.insert(users).values({
    id: userId,
    name: input.name.length ? input.name : null,
    email: normalizedEmail,
    password: hashedPassword,
    birthDate: input.birthDate ? new Date(input.birthDate) : null,
    age: input.age ?? null,
    guardianName: input.guardianName || null,
    guardianPhone: input.guardianPhone || null,
    guardianRelation: input.guardianRelation || null,
    consentAcceptedAt: new Date(),
  })

  // 查找默认角色（不自动创建）
  // audit-P2-3: 若 input.role 存在（邀请码注册），使用邀请码中的 role；否则使用 DEFAULT_REGISTER_ROLE
  const roleName = input.role ?? DEFAULT_REGISTER_ROLE
  const roleRow = await db.query.roles.findFirst({
    where: eq(roles.name, roleName),
    columns: { id: true },
  })
  if (!roleRow?.id) {
    throw new Error("DEFAULT_ROLE_NOT_FOUND")
  }
  await db.insert(usersToRoles).values({
    userId,
    roleId: roleRow.id,
  })

  return { userId }
}

/**
 * 解析注册输入为 RegisterInput（派生 age / isMinor）。
 *
 * 纯函数，便于单测。
 * audit-P2-3: 新增可选 role/classId 参数（邀请码注册时透传）。
 */
export function buildRegisterInput(formData: {
  name: string
  email: string
  password: string
  birthDate: string | null
  guardianName: string
  guardianPhone: string
  guardianRelation: string
  role?: string
  classId?: string
}): RegisterInput {
  const birthDate = formData.birthDate && formData.birthDate.length > 0
    ? formData.birthDate
    : null
  const age = birthDate ? calcAge(birthDate) : null
  const isMinor = birthDate ? isMinorByBirthDate(birthDate) : false

  return {
    name: formData.name,
    email: formData.email,
    password: formData.password,
    birthDate,
    age,
    isMinor,
    guardianName: formData.guardianName,
    guardianPhone: formData.guardianPhone,
    guardianRelation: formData.guardianRelation,
    role: formData.role,
    classId: formData.classId,
  }
}
