// POST /api/idempiere-auth/webauthn/login-verify
//   body: { response: AuthenticationResponseJSON, challengeToken?, pwa?, mobileWorker? }
//
// 1. consume the challenge (cookie or body token) + replay ledger
// 2. resolve the user by CREDENTIAL ID (webauthnIndex) — userHandle only as legacy fallback
// 3. verify the assertion, bump counter + lastUsedAt
// 4. decrypt the vaulted iDempiere password and run the EXISTING login pipeline:
//      web  → /api/idempiere-auth/login  (Set-Cookie forwarded to the browser)
//      app  → /api/app-auth/login        (X-Logship-App: 1; token + refresh_token in the body)
import { verifyAuthentication, decodeUserHandle } from '../../../utils/webauthnHelper'
import { consumeChallenge } from '../../../utils/webauthnChallengeStore'
import { readCredentials, writeCredentials, fetchUserName } from '../../../utils/adUserCredentials'
import { lookupCredential } from '../../../utils/webauthnIndex'
import { markChallengeUsed } from '../../../utils/webauthnReplayDb'
import { decryptPassword } from '../../../utils/passwordCipher'

export default defineEventHandler(async (event) => {
  const body = await readBody(event)
  const response = body?.response
  if (!response?.response?.clientDataJSON || typeof response?.id !== 'string') {
    throw createError({ statusCode: 400, statusMessage: 'Missing authentication response' })
  }

  // 1. Decode + consume challenge (signed cookie or body token issued by login-options)
  let clientData: any
  try {
    clientData = JSON.parse(Buffer.from(response.response.clientDataJSON, 'base64url').toString('utf8'))
  } catch {
    throw createError({ statusCode: 400, statusMessage: 'Malformed clientDataJSON' })
  }
  const challenge: string = String(clientData?.challenge ?? '')
  const entry = consumeChallenge(event, 'login', challenge, body?.challengeToken)
  if (!entry) {
    throw createError({ statusCode: 400, statusMessage: 'Challenge expired or invalid' })
  }
  if (!markChallengeUsed(challenge, 'login', entry.expiresAt)) {
    throw createError({ statusCode: 400, statusMessage: 'Challenge already used' })
  }

  // 2. Resolve user by credential id; legacy discoverable keys may still carry a userHandle.
  let userId: number | null = null
  const indexed = await lookupCredential(event, response.id).catch(() => null)
  if (indexed) userId = indexed.userId
  else userId = decodeUserHandle(response.response?.userHandle)
  if (!userId) {
    throw createError({ statusCode: 400, statusMessage: 'Credential not registered' })
  }

  // 3. Load credentials for this user (uses the iDempiere service token)
  const stored = await readCredentials(event, userId).catch(() => ({ keys: [], password: null }))
  const matching = stored.keys.find(k => k.credentialId === response.id)
  if (!matching) {
    throw createError({ statusCode: 400, statusMessage: 'Credential not registered' })
  }
  if (!stored.password) {
    throw createError({ statusCode: 400, statusMessage: 'No stored credentials — please re-register your security key' })
  }

  // 4. Verify the assertion
  const verification = await verifyAuthentication({
    response,
    expectedChallenge: challenge,
    credential: {
      id: matching.credentialId,
      publicKey: new Uint8Array(Buffer.from(matching.publicKey, 'base64')),
      counter: matching.counter,
      transports: matching.transports as any,
    },
  }).catch((e: any) => {
    throw createError({ statusCode: 400, statusMessage: 'Verification failed: ' + (e?.message ?? 'unknown error') })
  })

  if (!verification.verified) {
    throw createError({ statusCode: 400, statusMessage: 'Verification failed' })
  }

  // 5. Update counter + last-used (best effort — never block the login on this write)
  matching.counter = verification.authenticationInfo.newCounter
  matching.lastUsedAt = Date.now()
  await writeCredentials(event, userId, stored).catch(() => null)

  // 6. Decrypt the iDempiere password
  let password: string
  try {
    password = decryptPassword(stored.password)
  } catch (e: any) {
    throw createError({ statusCode: 500, statusMessage: 'Could not decrypt stored credentials: ' + (e?.message ?? 'unknown') })
  }

  // 7. Resolve username (the login id iDempiere expects)
  const userName = indexed?.userName || await fetchUserName(event, userId)
  if (!userName) {
    throw createError({ statusCode: 500, statusMessage: 'Could not resolve username for user ' + userId })
  }

  // 8. Delegate to the EXISTING login pipeline. login.post.ts is left untouched — it does the
  //    auth call, sets all cookies, runs auto-finalize. The native app (X-Logship-App: 1) goes
  //    through app-auth/login, which surfaces refresh_token in the body (no cookies cross-origin).
  const isPwa = !!body?.pwa
  const isMobileWorker = !!body?.mobileWorker || getCookie(event, 'logship_mw') === '1'
  const isApp = getHeader(event, 'x-logship-app') === '1'
  const loginBody = {
    userName,
    password,
    remember: false,
    selectRole: false,
    mobileWorker: isMobileWorker,
    pwa: isPwa,
  }

  if (isApp) {
    const data: any = await $fetch('/api/app-auth/login', {
      method: 'POST',
      headers: { cookie: getHeader(event, 'cookie') ?? '' },
      body: loginBody,
    }).catch((e: any) => {
      throw createError({
        statusCode: e?.statusCode ?? e?.status ?? 500,
        statusMessage: e?.statusMessage ?? e?.message ?? 'Login failed',
      })
    })
    return data
  }

  const loginRes = await $fetch.raw('/api/idempiere-auth/login', {
    method: 'POST',
    headers: {
      // forward existing cookies so client_id etc. may be available
      cookie: getHeader(event, 'cookie') ?? '',
    },
    body: loginBody,
  }).catch((e: any) => {
    throw createError({
      statusCode: e?.statusCode ?? e?.status ?? 500,
      statusMessage: e?.statusMessage ?? e?.message ?? 'Login failed',
    })
  })

  // Forward Set-Cookie headers from the internal login response to our caller.
  // Without this, the browser never sees logship_it / logship_user / etc.
  const setCookies: string[] = (loginRes.headers as any).getSetCookie?.()
    ?? (loginRes.headers as any).raw?.()['set-cookie']
    ?? []
  for (const c of setCookies) {
    appendResponseHeader(event, 'set-cookie', c)
  }

  return loginRes._data
})
