import "server-only"

import { compare } from "bcryptjs"
import { eq } from "drizzle-orm"

import { db } from "@/shared/db"
import { users, roles, usersToRoles, passwordSecurity } from "@/shared/db/schema"
import { logLoginEvent } from "@/shared/lib/login-logger"
import {
  PASSWORD_RULES,
  isAccountLocked,
} from "@/shared/lib/password-policy"
import { RATE_LIMIT_RULES, rateLimit, rateLimitKey, resetRateLimit } from "@/shared/lib/rate-limit"
import { normalizeBcryptHash } from "@/shared/lib/bcrypt-utils"
import { resolveClientIp } from "@/shared/lib/http-utils"
import {
  getOrCreatePasswordSecurity,
  recordFailedLogin,
  resetFailedLogin,
} from "@/shared/lib/password-security-service"
import { resolvePrimaryRole } from "@/shared/lib/role-utils"
import { trackAuthEvent } from "@/shared/lib/track-event"
import { verifyTwoFactorForLogin } from "./two-factor-service"

/**
 * Auth 模块登录服务（audit-P1-3 拆分）
 *
 * 此服务将 `auth.ts` 中 `authorize` 回调混合的 7 类职责抽取为独立纯函数：
 * 1. 输入解析与校验
 * 2. 速率限制（IP + email 双维度）
 * 3. 用户查询
 * 4. 账户锁定检查
 * 5. 密码校验（bcrypt + 失败计数）
 * 6. 2FA 校验（TOTP / 备用码）
 * 7. 角色加载与返回值构造
 *
 * 依赖关系：
 * - auth.ts → modules/auth/services/login-service（同模块，动态 import 避免 edge runtime 冲突）
 * - login-service → modules/auth/services/two-factor-service（同模块）
 * - login-service → shared/lib/*（基础设施层）
 * - login-service → shared/db + shared/db/schema（数据访问层）
 *
 * 设计目标：
 * - `auth.ts` 的 authorize 回调降至 3 行（薄包装调用 authenticateUser）
 * - 所有登录逻辑可独立测试（无需启动 NextAuth）
 * - 错误路径统一通过 logLoginEvent 记录，返回 null 表示失败
 */

/** authorize 回调收到的原始 credentials（字段均为 unknown） */
export interface AuthenticateUserInput {
  email?: unknown
  password?: unknown
  totpCode?: unknown
}

/** 认证成功后的用户对象（与 next-auth User 接口兼容） */
export interface AuthenticatedUser {
  id: string
  name?: string
  email: string
  role: string
  roles: string[]
}

/**
 * 认证用户：从原始 credentials 解析并校验，返回用户对象或 null。
 *
 * 失败路径均通过 logLoginEvent 记录审计日志，返回 null。
 * 成功路径重置失败计数与速率限制，返回包含角色信息的用户对象。
 */
export async function authenticateUser(
  credentials: AuthenticateUserInput,
): Promise<AuthenticatedUser | null> {
  // 1. 输入解析与校验
  const email = String(credentials?.email ?? "").trim().toLowerCase()
  const password = String(credentials?.password ?? "")
  const totpCode = String(credentials?.totpCode ?? "").trim()
  if (!email || !password) return null

  // 2. 速率限制（IP + email 双维度，减缓暴力破解）
  const clientIp = await resolveClientIp()
  const loginLimitKey = rateLimitKey("login", `${clientIp}:${email}`)
  const limit = await rateLimit({
    key: loginLimitKey,
    ...RATE_LIMIT_RULES.LOGIN,
  })
  if (!limit.success) {
    await logLoginEvent({
      userEmail: email,
      action: "signin",
      status: "failure",
      errorMessage: "Rate limit exceeded",
    })
    // audit-P1-9：限流触发埋点（用于限流触发率、异常登录地理/设备告警）
    await trackAuthEvent("auth.rate_limited", {
      properties: {
        email,
        clientIp,
        limit: RATE_LIMIT_RULES.LOGIN.limit,
        windowMs: RATE_LIMIT_RULES.LOGIN.windowMs,
      },
    })
    return null
  }

  // 3. 用户查询
  const user = await db.query.users.findFirst({
    where: eq(users.email, email),
  })
  if (!user) return null

  // 4. 账户锁定检查
  const security = await getOrCreatePasswordSecurity(db, passwordSecurity, user.id)
  const lastFailedAt = security.lockedUntil
    ? new Date(security.lockedUntil.getTime() - PASSWORD_RULES.lockoutDurationMinutes * 60 * 1000)
    : null
  if (isAccountLocked(security.failedLoginAttempts, lastFailedAt)) {
    await logLoginEvent({
      userId: user.id,
      userEmail: email,
      action: "signin",
      status: "failure",
      errorMessage: "Account locked",
    })
    // audit-P1-9：账户锁定埋点（用于账户锁定触发率告警）
    await trackAuthEvent("auth.account_locked", {
      userId: user.id,
      properties: {
        email,
        failedAttempts: security.failedLoginAttempts,
        lockedUntil: security.lockedUntil?.toISOString(),
      },
    })
    return null
  }

  // 5. 密码校验（bcrypt + 失败计数）
  const storedPassword = user.password ?? null
  if (!storedPassword) return null
  const normalizedPassword = normalizeBcryptHash(storedPassword)
  if (!normalizedPassword.startsWith("$2")) return null
  const ok = await compare(password, normalizedPassword)

  if (!ok) {
    await recordFailedLogin(db, passwordSecurity, user.id)
    await logLoginEvent({
      userId: user.id,
      userEmail: email,
      action: "signin",
      status: "failure",
      errorMessage: "Invalid credentials",
    })
    // audit-P1-9：登录失败埋点（密码错误，用于登录成功率、异常登录尝试告警）
    // 注意：不区分"用户不存在"和"密码错误"以防用户枚举，但此处已知用户存在
    await trackAuthEvent("auth.signin_failure", {
      userId: user.id,
      properties: {
        email,
        reason: "invalid_password",
        failedAttempts: security.failedLoginAttempts + 1,
      },
    })
    return null
  }

  // 成功登录：重置失败计数与速率限制
  await resetFailedLogin(db, passwordSecurity, user.id)
  await resetRateLimit(loginLimitKey)

  // 6. 2FA 校验（TOTP / 备用码，可选）
  const twoFactorResult = await verifyTwoFactorForLogin({
    userId: user.id,
    token: totpCode || undefined,
  })
  if (twoFactorResult.required && !twoFactorResult.valid) {
    await logLoginEvent({
      userId: user.id,
      userEmail: email,
      action: "signin",
      status: "failure",
      errorMessage: totpCode
        ? "Invalid 2FA code"
        : "2FA required but not provided",
    })
    // audit-P1-9：2FA 校验失败埋点（用于 2FA 失败率、暴力 2FA 尝试告警）
    await trackAuthEvent("auth.signin_failure", {
      userId: user.id,
      properties: {
        email,
        reason: totpCode ? "invalid_2fa_code" : "2fa_required_not_provided",
      },
    })
    return null
  }

  // 7. 角色加载与返回值构造
  const roleRows = await db
    .select({ name: roles.name })
    .from(usersToRoles)
    .innerJoin(roles, eq(usersToRoles.roleId, roles.id))
    .where(eq(usersToRoles.userId, user.id))

  const roleNames = roleRows.map((r) => r.name)
  const resolvedRole = resolvePrimaryRole(roleNames)

  return {
    id: user.id,
    name: user.name ?? undefined,
    email: user.email,
    role: resolvedRole,
    roles: roleNames,
  }
}
