import {
  generateRegistrationOptions,
  verifyRegistrationResponse,
  generateAuthenticationOptions,
  verifyAuthenticationResponse,
} from '@simplewebauthn/server'
import type {
  RegistrationResponseJSON,
  AuthenticationResponseJSON,
} from '@simplewebauthn/server'

// PIN-free security-key login (see .claude/rules/auth/webauthn-security-keys.md):
//  * credentials are NON-discoverable (residentKey 'discouraged') — a YubiKey can only hold a
//    discoverable credential when a PIN is set, and Chrome/Windows then prompt for that PIN on
//    every login (credProtect escalation). Non-discoverable + allowCredentials needs no PIN.
//  * userVerification 'discouraged' on both ceremonies — presence (touch / NFC tap) only.
//  * the login therefore identifies the user by CREDENTIAL ID (webauthnIndex.ts), never by the
//    userHandle, which non-discoverable credentials do not return.

export interface RpConfig {
  rpID: string
  rpName: string
  origin: string
  origins: string[]
  globalAllowlist: boolean
  globalAllowlistMax: number
}

export function getRpConfig(): RpConfig {
  const config = useRuntimeConfig()
  const wa: any = (config as any).webauthn ?? {}
  const rpID: string | undefined = wa.rpId
  const rpName: string = wa.rpName ?? 'LogShip ERP'
  const origin: string | undefined = wa.origin
  if (!rpID || !origin) {
    throw new Error('WEBAUTHN_RP_ID and WEBAUTHN_ORIGIN env vars must be set')
  }
  // Extra accepted origins: the Android app signs with its APK key hash
  // (`android:apk-key-hash:<base64url sha256 of the signing cert>`), optionally more hosts.
  const extra = String(wa.extraOrigins ?? '')
    .split(',')
    .map(s => s.trim())
    .filter(Boolean)
  const origins = Array.from(new Set([origin, ...extra]))
  const globalAllowlist = String(wa.globalAllowlist ?? '1') !== '0'
  const maxRaw = Number(wa.globalAllowlistMax)
  const globalAllowlistMax = Number.isFinite(maxRaw) && maxRaw > 0 ? maxRaw : 150
  return { rpID, rpName, origin, origins, globalAllowlist, globalAllowlistMax }
}

// numeric ad_user.id encoded as Uint8Array for the WebAuthn userHandle
function encodeUserHandle(userId: number): Uint8Array {
  // Buffer.from(...) returns a Buffer (extends Uint8Array<ArrayBuffer>); the
  // TextEncoder path produces Uint8Array<ArrayBufferLike> which TS rejects
  // for the simplewebauthn typing on userID.
  return Buffer.from(String(userId), 'utf8')
}

export function decodeUserHandle(handle: string | undefined | null): number | null {
  if (!handle) return null
  try {
    const decoded = Buffer.from(handle, 'base64url').toString('utf8')
    const n = Number(decoded)
    return Number.isFinite(n) && n > 0 ? n : null
  } catch {
    return null
  }
}

export async function buildRegistrationOptions(opts: {
  userId: number
  userName: string
  userDisplayName?: string
  excludeCredentialIds?: string[]
}) {
  const { rpID, rpName } = getRpConfig()
  return generateRegistrationOptions({
    rpName,
    rpID,
    userName: opts.userName,
    userID: encodeUserHandle(opts.userId) as any,
    userDisplayName: opts.userDisplayName ?? opts.userName,
    attestationType: 'none',
    authenticatorSelection: {
      // Non-discoverable: no PIN needed on a YubiKey, no credProtect PIN escalation.
      residentKey: 'discouraged',
      requireResidentKey: false,
      userVerification: 'discouraged',
      // Roaming keys only — keeps Windows Hello / Touch ID out of the enrolment prompt.
      authenticatorAttachment: 'cross-platform',
    },
    excludeCredentials: (opts.excludeCredentialIds ?? []).map(id => ({ id })),
  })
}

export async function verifyRegistration(opts: {
  response: RegistrationResponseJSON
  expectedChallenge: string
}) {
  const { rpID, origins } = getRpConfig()
  return verifyRegistrationResponse({
    response: opts.response,
    expectedChallenge: opts.expectedChallenge,
    expectedOrigin: origins,
    expectedRPID: rpID,
    requireUserVerification: false,
  })
}

export interface AllowCredential {
  id: string
  transports?: any[]
}

const ROAMING = ['usb', 'nfc']
function roamingTransports(recorded?: any[]): string[] {
  const t = Array.isArray(recorded) ? recorded.map(String) : []
  const isRoaming = !t.length || t.some(x => x === 'usb' || x === 'nfc' || x === 'ble')
  if (!isRoaming) return t
  return Array.from(new Set([...ROAMING, ...t]))
}

export async function buildAuthenticationOptions(opts: { allowCredentials: AllowCredential[] }) {
  const { rpID } = getRpConfig()
  return generateAuthenticationOptions({
    rpID,
    userVerification: 'discouraged',
    // Populated allow-list → non-discoverable credentials work, no PIN. Transport hints steer the
    // platform UI: Android Credential Manager only arms NFC when 'nfc' is listed, and a key enrolled
    // over USB on a desktop records ['usb'] only. Roaming keys therefore always advertise usb + nfc
    // (a USB-only key simply never answers on NFC); platform/hybrid hints are kept as recorded.
    allowCredentials: opts.allowCredentials.map(c => ({
      id: c.id,
      transports: roamingTransports(c.transports) as any,
    })),
  })
}

export async function verifyAuthentication(opts: {
  response: AuthenticationResponseJSON
  expectedChallenge: string
  credential: { id: string; publicKey: Uint8Array; counter: number; transports?: any[] }
}) {
  const { rpID, origins } = getRpConfig()
  return verifyAuthenticationResponse({
    response: opts.response,
    expectedChallenge: opts.expectedChallenge,
    expectedOrigin: origins,
    expectedRPID: rpID,
    credential: opts.credential as any,
    requireUserVerification: false,
  })
}
