import crypto from 'node:crypto'
import type { JsonObject } from '../../types/json.js'

import { runtimeConfig } from '../../config/runtime.js'
import {
  countActiveAdminUsers,
  createAdminUser,
  getAdminUserById,
  getAdminUserByUsername,
  listAdminUsers,
  updateAdminUser,
} from '../../repositories/admin-user-repo.js'
import {
  createAdminSession as createAdminSessionRecord,
  getActiveAdminSession,
  revokeAdminSession,
  touchAdminSession,
} from '../../repositories/admin-session-repo.js'
import { addHours, nowIso } from '../../utils/time.js'
import { createHttpError } from '../../utils/http.js'
import { normalizePage, normalizePageSize } from './admin-query-utils.js'
import { recordAdminLoginLog } from './admin-login-log-service.js'
import { listAdminPermissionKeysForRole } from './admin-rbac-service.js'

type AdminUserRow = NonNullable<Awaited<ReturnType<typeof getAdminUserById>>>

export type AdminSession = {
  sessionId: string
  userId: number
  username: string
  role: AdminRole
  expiresAt: string
  sessionVersion: number
  permissions: string[]
}

type AdminRole = 'admin' | 'operator' | 'support' | 'finance'
type AdminUserStatus = 'active' | 'disabled'

export async function ensureAdminUsersBootstrapped(): Promise<void> {
  ensureAdminAuthConfigured()

  const configuredUsers = Array.isArray(runtimeConfig.admin?.defaultUsers)
    ? runtimeConfig.admin.defaultUsers
    : []

  for (const configuredUser of configuredUsers) {
    const username = String(configuredUser?.username || '')
      .trim()
      .toLowerCase()
    const password = String(configuredUser?.password || '').trim()
    const role = normalizeAdminRole(configuredUser?.role)

    if (!username || !password) {
      continue
    }

    if (await getAdminUserByUsername(username)) {
      continue
    }

    const now = nowIso()
    await createAdminUser({
      username,
      passwordHash: hashAdminPassword(password),
      role,
      status: 'active',
      createdAt: now,
      updatedAt: now,
    })
  }
}

export async function loginAdmin(
  username: unknown,
  password: unknown,
  meta: {
    ip?: string
    userAgent?: string
    location?: string
  } = {},
): Promise<JsonObject> {
  ensureAdminAuthConfigured()

  const normalizedUsername = String(username || '')
    .trim()
    .toLowerCase()
  const normalizedPassword = String(password || '').trim()

  if (!normalizedUsername || !normalizedPassword) {
    await recordAdminLoginLog({
      username: normalizedUsername,
      success: false,
      failureReason: 'missing_credentials',
      ...pickLoginMeta(meta),
    })
    throw createHttpError('缺少后台账号或密码', {
      statusCode: 400,
      errorCode: 'admin_credentials_required',
    })
  }

  const user = await getAdminUserByUsername(normalizedUsername)
  if (
    !user ||
    user.status !== 'active' ||
    !verifyAdminPassword(normalizedPassword, user.password_hash)
  ) {
    await recordAdminLoginLog({
      userId: user ? Number(user.id) : null,
      username: normalizedUsername,
      role: user ? normalizeAdminRole(user.role) : '',
      success: false,
      failureReason: 'invalid_credentials',
      ...pickLoginMeta(meta),
    })
    throw createHttpError('账号或密码错误', {
      statusCode: 401,
      errorCode: 'admin_login_failed',
    })
  }

  const session = createAdminSession(user)
  const createdSession = await createAdminSessionRecord({
    sessionId: String(session.sessionId || ''),
    userId: Number(user.id),
    issuedAt: String(session.issuedAt),
    expiresAt: String(session.expiresAt),
    now: nowIso(),
  })
  if (!createdSession) {
    throw createHttpError('后台登录会话创建失败，请重试', {
      statusCode: 503,
      errorCode: 'admin_session_create_failed',
    })
  }
  await recordAdminLoginLog({
    userId: Number(user.id),
    username: String(user.username || normalizedUsername),
    role: normalizeAdminRole(user.role),
    success: true,
    ...pickLoginMeta(meta),
  })
  return session
}

export async function verifyAdminSessionToken(token: unknown): Promise<AdminSession> {
  ensureAdminAuthConfigured()

  const normalizedToken = String(token || '').trim()
  if (!normalizedToken) {
    throw createHttpError('未登录或登录已失效', {
      statusCode: 401,
      errorCode: 'admin_auth_required',
    })
  }

  const parts = normalizedToken.split('.')
  const encodedPayload = parts[0]
  const signature = parts[1]
  if (parts.length !== 2 || !encodedPayload || !signature) {
    throw createHttpError('后台登录态无效', {
      statusCode: 401,
      errorCode: 'admin_auth_invalid',
    })
  }

  const expectedSignature = signPayload(encodedPayload)
  if (!safeCompare(signature, expectedSignature)) {
    throw createHttpError('后台登录态无效', {
      statusCode: 401,
      errorCode: 'admin_auth_invalid',
    })
  }

  let payload: JsonObject | null

  try {
    payload = JSON.parse(Buffer.from(encodedPayload, 'base64url').toString('utf8'))
  } catch {
    throw createHttpError('后台登录态无效', {
      statusCode: 401,
      errorCode: 'admin_auth_invalid',
    })
  }

  const expiresAt = String(payload?.exp || '').trim()
  if (!expiresAt || Date.parse(expiresAt) <= Date.now()) {
    throw createHttpError('后台登录已过期，请重新登录', {
      statusCode: 401,
      errorCode: 'admin_auth_expired',
    })
  }

  const userId = Number(payload?.uid || 0)
  const sessionId = String(payload?.sid || '').trim()
  if (!sessionId) {
    throw createHttpError('后台登录态无效', {
      statusCode: 401,
      errorCode: 'admin_auth_invalid',
    })
  }
  const activeSession = await getActiveAdminSession(sessionId, nowIso())
  if (!activeSession || Number(activeSession.user_id) !== userId) {
    throw createHttpError('后台登录态已失效，请重新登录', {
      statusCode: 401,
      errorCode: 'admin_auth_revoked',
    })
  }
  await touchAdminSession(sessionId, nowIso())
  const user = await getAdminUserById(userId)
  if (!user || user.status !== 'active') {
    throw createHttpError('后台账号已不可用，请重新登录', {
      statusCode: 401,
      errorCode: 'admin_auth_user_invalid',
    })
  }

  const tokenSessionVersion = Number(payload?.ver || 0)
  const currentSessionVersion = normalizeAdminSessionVersion(user.session_version)
  if (tokenSessionVersion !== currentSessionVersion) {
    throw createHttpError('后台登录态已失效，请重新登录', {
      statusCode: 401,
      errorCode: 'admin_auth_stale',
    })
  }

  return {
    sessionId,
    userId: Number(user.id),
    username: String(user.username || ''),
    role: normalizeAdminRole(user.role),
    expiresAt,
    sessionVersion: currentSessionVersion,
    permissions: await listAdminPermissionKeysForRole(normalizeAdminRole(user.role)),
  }
}

export async function logoutAdmin(token: unknown): Promise<{ success: boolean; revoked: boolean }> {
  const session = await verifyAdminSessionToken(token)
  const revoked = await revokeAdminSession(session.sessionId, nowIso())
  return { success: true, revoked }
}

export async function getAdminSessionSummary(token: unknown): Promise<JsonObject> {
  const session = await verifyAdminSessionToken(token)

  return {
    authenticated: true,
    expiresAt: session.expiresAt,
    user: {
      userId: session.userId,
      username: session.username,
      role: session.role,
      permissions: session.permissions,
    },
  }
}

export function requireAdminRole(
  session: { role?: string } | null | undefined,
  allowedRoles: string[],
): void {
  if (session && allowedRoles.includes(session.role || '')) {
    return
  }

  throw createHttpError('当前账号没有此操作权限', {
    statusCode: 403,
    errorCode: 'admin_permission_denied',
  })
}

export async function getAdminUserList(query: JsonObject = {}): Promise<JsonObject> {
  const page = normalizePage(query.page)
  const pageSize = normalizePageSize(query.pageSize)
  const { items, total } = await listAdminUsers({
    page,
    pageSize,
    username: String(query.username || '').trim(),
    role: normalizeRoleQuery(query.role),
    status: normalizeStatusQuery(query.status),
  })

  return {
    items: items.map(mapAdminUser),
    pagination: { page, pageSize, total },
  }
}

export async function createManagedAdminUser(payload: JsonObject = {}): Promise<JsonObject> {
  const username = normalizeUsername(payload.username)
  const password = normalizePassword(payload.password)

  if (!username) {
    throw createHttpError('缺少后台账号', {
      statusCode: 400,
      errorCode: 'admin_user_username_required',
    })
  }

  if (!password) {
    throw createHttpError('缺少后台密码', {
      statusCode: 400,
      errorCode: 'admin_user_password_required',
    })
  }

  validateUsername(username)
  validatePassword(password)

  if (await getAdminUserByUsername(username)) {
    throw createHttpError('后台账号已存在', {
      statusCode: 409,
      errorCode: 'admin_user_exists',
    })
  }

  const now = nowIso()
  const created = await createAdminUser({
    username,
    passwordHash: hashAdminPassword(password),
    role: normalizeAdminRole(payload.role),
    status: normalizeAdminUserStatus(payload.status || 'active'),
    createdAt: now,
    updatedAt: now,
  })
  if (!created) {
    throw createHttpError('后台用户创建失败', {
      statusCode: 500,
      errorCode: 'admin_user_create_failed',
    })
  }

  return {
    user: mapAdminUser(created),
  }
}

export async function updateManagedAdminUserRole(
  userId: number | string,
  payload: JsonObject = {},
  session: AdminSession,
): Promise<JsonObject> {
  const user = await getRequiredAdminUser(userId)
  const role = normalizeAdminRole(payload.role)

  if (user.role === role) {
    return {
      user: mapAdminUser(user),
    }
  }

  await ensureAdminUserChangeAllowed(user, { nextRole: role }, session)
  const updated = await updateAdminUser(user.id, {
    role,
    session_version: nextAdminSessionVersion(user),
    updated_at: nowIso(),
  })
  if (!updated) {
    throw createHttpError('后台用户更新失败', {
      statusCode: 500,
      errorCode: 'admin_user_update_failed',
    })
  }

  return {
    user: mapAdminUser(updated),
  }
}

export async function updateManagedAdminUserStatus(
  userId: number | string,
  payload: JsonObject = {},
  session: AdminSession,
): Promise<JsonObject> {
  const user = await getRequiredAdminUser(userId)
  const status = normalizeAdminUserStatus(payload.status)

  if (user.status === status) {
    return {
      user: mapAdminUser(user),
    }
  }

  await ensureAdminUserChangeAllowed(user, { nextStatus: status }, session)
  const updated = await updateAdminUser(user.id, {
    status,
    session_version: nextAdminSessionVersion(user),
    updated_at: nowIso(),
  })
  if (!updated) {
    throw createHttpError('后台用户更新失败', {
      statusCode: 500,
      errorCode: 'admin_user_update_failed',
    })
  }

  return {
    user: mapAdminUser(updated),
  }
}

export async function resetManagedAdminUserPassword(
  userId: number | string,
  payload: JsonObject = {},
): Promise<JsonObject> {
  const user = await getRequiredAdminUser(userId)
  const password = normalizePassword(payload.password)

  if (!password) {
    throw createHttpError('缺少新密码', {
      statusCode: 400,
      errorCode: 'admin_user_password_required',
    })
  }

  validatePassword(password)
  const updated = await updateAdminUser(user.id, {
    password_hash: hashAdminPassword(password),
    session_version: nextAdminSessionVersion(user),
    updated_at: nowIso(),
  })
  if (!updated) {
    throw createHttpError('后台用户更新失败', {
      statusCode: 500,
      errorCode: 'admin_user_update_failed',
    })
  }

  return {
    user: mapAdminUser(updated),
  }
}

export function ensureAdminAuthConfigured(): void {
  const sessionSecret = String(runtimeConfig.admin?.sessionSecret || '').trim()
  const configuredUsers = Array.isArray(runtimeConfig.admin?.defaultUsers)
    ? runtimeConfig.admin.defaultUsers
    : []

  if (sessionSecret && configuredUsers.length > 0) {
    return
  }

  throw createHttpError('后台鉴权未配置，请先设置 admin.sessionSecret 和 admin.defaultUsers', {
    statusCode: 503,
    errorCode: 'admin_auth_not_configured',
  })
}

function createAdminSession(user: AdminUserRow): JsonObject {
  const issuedAt = nowIso()
  const expiresAt = addHours(issuedAt, Number(runtimeConfig.admin.sessionTtlHours || 12))
  const payload = {
    sid: crypto.randomBytes(12).toString('hex'),
    uid: Number(user.id),
    usr: String(user.username || ''),
    role: normalizeAdminRole(user.role),
    ver: normalizeAdminSessionVersion(user.session_version),
    iat: issuedAt,
    exp: expiresAt,
  }
  const encodedPayload = Buffer.from(JSON.stringify(payload)).toString('base64url')
  const signature = signPayload(encodedPayload)

  return {
    sessionId: payload.sid,
    issuedAt,
    token: `${encodedPayload}.${signature}`,
    expiresAt,
    user: {
      userId: Number(user.id),
      username: String(user.username || ''),
      role: normalizeAdminRole(user.role),
    },
  }
}

export function hashAdminPassword(password: string): string {
  const salt = crypto.randomBytes(16).toString('hex')
  const derived = crypto.scryptSync(password, salt, 64).toString('hex')
  return `scrypt$${salt}$${derived}`
}

function verifyAdminPassword(password: string, storedHash: string): boolean {
  const [algorithm, salt, expectedHash] = String(storedHash || '').split('$')
  if (algorithm !== 'scrypt' || !salt || !expectedHash) {
    return false
  }

  const actualHash = crypto.scryptSync(password, salt, 64).toString('hex')
  return safeCompare(actualHash, expectedHash)
}

function signPayload(encodedPayload: string): string {
  return crypto
    .createHmac('sha256', String(runtimeConfig.admin.sessionSecret || ''))
    .update(encodedPayload)
    .digest('base64url')
}

export function normalizeAdminRole(role: unknown): AdminRole {
  const normalized = String(role || '')
    .trim()
    .toLowerCase()

  if (normalized === 'admin') {
    return 'admin'
  }

  if (normalized === 'support') {
    return 'support'
  }

  if (normalized === 'finance') {
    return 'finance'
  }

  return 'operator'
}

export function normalizeAdminUserStatus(status: unknown): AdminUserStatus {
  return String(status || '')
    .trim()
    .toLowerCase() === 'disabled'
    ? 'disabled'
    : 'active'
}

function safeCompare(input: unknown, expected: unknown): boolean {
  const left = Buffer.from(String(input || ''), 'utf8')
  const right = Buffer.from(String(expected || ''), 'utf8')

  if (left.length !== right.length) {
    return false
  }

  return crypto.timingSafeEqual(left, right)
}

function normalizeRoleQuery(role: unknown): string {
  const normalized = String(role || '')
    .trim()
    .toLowerCase()
  return ['admin', 'operator', 'support', 'finance'].includes(normalized) ? normalized : ''
}

function normalizeStatusQuery(status: unknown): string {
  const normalized = String(status || '')
    .trim()
    .toLowerCase()
  return ['active', 'disabled'].includes(normalized) ? normalized : ''
}

function normalizeUsername(username: unknown): string {
  return String(username || '')
    .trim()
    .toLowerCase()
}

function normalizePassword(password: unknown): string {
  return String(password || '').trim()
}

function validateUsername(username: string): void {
  if (!/^[a-zA-Z0-9._-]{3,32}$/.test(username)) {
    throw createHttpError('后台账号格式无效，需为 3-32 位字母数字或 ._-', {
      statusCode: 400,
      errorCode: 'admin_user_username_invalid',
    })
  }
}

function validatePassword(password: string): void {
  if (password.length < 8) {
    throw createHttpError('后台密码至少 8 位', {
      statusCode: 400,
      errorCode: 'admin_user_password_invalid',
    })
  }
}

function normalizeAdminSessionVersion(value: unknown): number {
  const parsed = Number(value)
  return Number.isInteger(parsed) && parsed > 0 ? parsed : 1
}

function nextAdminSessionVersion(user: AdminUserRow): number {
  return normalizeAdminSessionVersion(user.session_version) + 1
}

async function getRequiredAdminUser(userId: number | string): Promise<AdminUserRow> {
  const user = await getAdminUserById(Number(userId))

  if (!user) {
    throw createHttpError('后台用户不存在', {
      statusCode: 404,
      errorCode: 'admin_user_not_found',
    })
  }

  return user
}

async function ensureAdminUserChangeAllowed(
  user: AdminUserRow,
  options: { nextRole?: AdminRole; nextStatus?: AdminUserStatus } = {},
  session: AdminSession,
): Promise<void> {
  const nextRole = options.nextRole || user.role
  const nextStatus = options.nextStatus || user.status

  if (session?.userId === Number(user.id) && (nextRole !== 'admin' || nextStatus !== 'active')) {
    throw createHttpError('不能停用或降级当前登录账号', {
      statusCode: 409,
      errorCode: 'admin_user_self_change_not_allowed',
    })
  }

  if (
    user.role === 'admin' &&
    (nextRole !== 'admin' || nextStatus !== 'active') &&
    (await countActiveAdminUsers()) <= 1
  ) {
    throw createHttpError('至少保留一个启用中的管理员账号', {
      statusCode: 409,
      errorCode: 'admin_user_last_admin_not_allowed',
    })
  }
}

function mapAdminUser(user: AdminUserRow): JsonObject {
  return {
    userId: Number(user.id),
    username: String(user.username || ''),
    role: normalizeAdminRole(user.role),
    status: normalizeAdminUserStatus(user.status),
    createdAt: user.created_at,
    updatedAt: user.updated_at,
  }
}

function pickLoginMeta(
  meta: {
    ip?: string
    userAgent?: string
    location?: string
  } = {},
) {
  const result: {
    ip?: string
    userAgent?: string
    location?: string
  } = {}

  if (typeof meta.ip === 'string' && meta.ip.trim()) {
    result.ip = meta.ip.trim()
  }
  if (typeof meta.userAgent === 'string' && meta.userAgent.trim()) {
    result.userAgent = meta.userAgent.trim()
  }
  if (typeof meta.location === 'string' && meta.location.trim()) {
    result.location = meta.location.trim()
  }

  return result
}
