import {
  startRegistration,
  startAuthentication,
  browserSupportsWebAuthn,
  WebAuthnAbortService,
} from '@simplewebauthn/browser'
import { Capacitor, registerPlugin } from '@capacitor/core'

export interface RegisteredKey {
  credentialId: string
  nickname: string
  createdAt: number
  transports: string[]
  deviceType: string | null
  lastUsedAt?: number | null
  enrolledBy?: number | null
}

// Native Android bridge (android/app/src/main/java/com/logship/mobile/WebAuthnPlugin.java).
// Default mode "key": YubiKit runs the ceremony inside the app (own "hold the key" dialog, NFC
// reader mode + USB) and signs for the WEB origin, so no Credential Manager sheet, no Play
// services and no asset links are involved. Mode "system" = Android Credential Manager fallback.
// Both methods take and return the same JSON shapes as @simplewebauthn/browser.
export interface NativeKeyCapabilities {
  available: boolean
  nfc?: boolean
  nfcEnabled?: boolean
  usb?: boolean
  system?: boolean
  yubikit?: boolean
  error?: string
}
interface NativeCeremonyOptions {
  requestJson: string
  mode?: 'key' | 'system'
  origin?: string
  title?: string
  message?: string
  detectedMessage?: string
  touchMessage?: string
  retryMessage?: string
  nfcOffMessage?: string
  cancelLabel?: string
}
interface NativeWebAuthnPlugin {
  isAvailable(): Promise<NativeKeyCapabilities>
  create(options: NativeCeremonyOptions): Promise<{ responseJson: string; transport?: string }>
  get(options: NativeCeremonyOptions): Promise<{ responseJson: string; transport?: string }>
  cancel(): Promise<void>
}
const NativeWebAuthn = registerPlugin<NativeWebAuthnPlugin>('WebAuthn')
const isNative = () => typeof window !== 'undefined' && Capacitor.isNativePlatform()
// The origin the in-app ceremony signs for — must be WEBAUTHN_ORIGIN on the server (the app build
// carries it as public.apiBase, default https://app.logship.de).
const DEFAULT_WEB_ORIGIN = 'https://app.logship.de'

// Normalise plugin failures to the DOMException names the web flow already handles.
function nativeError(e: any): Error {
  const msg = String(e?.message ?? e ?? '')
  const code = String(e?.code ?? '')
  const err: any = new Error(msg || 'Security key operation failed')
  if (code === 'NotAllowedError' || /cancel/i.test(msg)) err.name = 'NotAllowedError'
  else if (code === 'InvalidStateError') err.name = 'InvalidStateError'
  else if (code) err.name = code
  return err
}

const nativeCreate = async (optionsJSON: any, opts: Omit<NativeCeremonyOptions, 'requestJson'>) => {
  try {
    const r = await NativeWebAuthn.create({ ...opts, requestJson: JSON.stringify(optionsJSON) })
    return JSON.parse(r.responseJson)
  } catch (e) { throw nativeError(e) }
}
const nativeGet = async (optionsJSON: any, opts: Omit<NativeCeremonyOptions, 'requestJson'>) => {
  try {
    const r = await NativeWebAuthn.get({ ...opts, requestJson: JSON.stringify(optionsJSON) })
    return JSON.parse(r.responseJson)
  } catch (e) { throw nativeError(e) }
}

// Strip our extra fields before handing the options to the authenticator.
const pickOptions = (res: any) => {
  const { challengeToken, credentialCount, needsUserName, targetUser, ...options } = res ?? {}
  return options
}

export const useWebAuthn = () => {
  let nativeAvailable: boolean | null = null
  const capabilities = ref<NativeKeyCapabilities | null>(null)
  const { t } = useTranslation()
  const config = useRuntimeConfig()

  // Texts for the native "hold the key" dialog + the origin the in-app ceremony signs for.
  const nativeOpts = (create: boolean): Omit<NativeCeremonyOptions, 'requestJson'> => {
    const tr: any = t.value || {}
    const apiBase = String((config.public as any)?.apiBase || '').replace(/\/+$/, '')
    return {
      mode: 'key',
      origin: apiBase || DEFAULT_WEB_ORIGIN,
      title: create
        ? (tr.security_key_enrol_dialog_title || 'Enrol security key')
        : (tr.security_key_dialog_title || 'Security key'),
      message: tr.security_key_dialog_message || 'Hold your YubiKey flat against the back of the phone, or plug it into the USB port.',
      detectedMessage: tr.security_key_dialog_detected || 'Key detected – keep holding it still…',
      touchMessage: tr.security_key_dialog_touch || 'Key connected – touch the gold contact on the key.',
      retryMessage: tr.security_key_dialog_retry || 'Connection lost – hold the key to the phone again and keep it still.',
      nfcOffMessage: tr.security_key_dialog_nfc_off || 'NFC is switched off on this phone. Enable it in the settings or plug the key in via USB.',
      cancelLabel: tr.cancel || 'Cancel',
    }
  }

  /** Synchronous capability check (web). The native plugin is probed by isAvailable(). */
  const isSupported = (): boolean => {
    if (typeof window === 'undefined') return false
    if (isNative()) return nativeAvailable !== false
    try {
      return browserSupportsWebAuthn()
    } catch {
      return false
    }
  }

  /** Async capability check incl. the native plugin — use this on mount. */
  const isAvailable = async (): Promise<boolean> => {
    if (typeof window === 'undefined') return false
    if (isNative()) {
      try {
        const r = await NativeWebAuthn.isAvailable()
        capabilities.value = r
        nativeAvailable = r?.available !== false
      } catch (e: any) {
        // The plugin is compiled into every app build — a rejection here means it failed to load.
        // Keep the button visible so the tap surfaces the reason instead of silently vanishing.
        capabilities.value = { available: true, error: 'WebAuthn plugin unavailable: ' + String(e?.message ?? e) }
        nativeAvailable = true
      }
      return nativeAvailable
    }
    return isSupported()
  }

  /** Abort a running in-app ceremony (page leaves while the dialog is open). */
  const cancelNative = async () => {
    if (!isNative()) return
    try { await NativeWebAuthn.cancel() } catch {}
  }

  const createCredential = async (optionsJSON: any) => {
    if (isNative()) return nativeCreate(optionsJSON, nativeOpts(true))
    try { WebAuthnAbortService.cancelCeremony() } catch {}
    return startRegistration({ optionsJSON })
  }

  const getAssertion = async (optionsJSON: any) => {
    if (isNative()) {
      if (capabilities.value?.error && capabilities.value?.yubikit === false) {
        const err: any = new Error(capabilities.value.error)
        err.name = 'NotSupportedError'
        throw err
      }
      return nativeGet(optionsJSON, nativeOpts(false))
    }
    // After a user-cancelled prompt, the lib's internal AbortController can still hold the
    // previous signal. Without resetting it, calling startAuthentication again silently no-ops
    // in some browsers and the user has to reload the page.
    try { WebAuthnAbortService.cancelCeremony() } catch {}
    return startAuthentication({ optionsJSON })
  }

  /**
   * Enrol a key. Without `forUserId` → the logged-in user's own key (profile page). With it →
   * admin enrolment for that employee through /api/admin/users/[id]/webauthn/*.
   */
  const register = async (nickname: string, opts: { forUserId?: number } = {}) => {
    const base = opts.forUserId
      ? `/api/admin/users/${opts.forUserId}/webauthn`
      : '/api/idempiere-auth/webauthn'
    const res: any = await $fetch(`${base}/register-options`, {
      method: 'POST',
      headers: useRequestHeaders(['cookie']),
    })
    const attestation = await createCredential(pickOptions(res))
    return await $fetch(`${base}/register-verify`, {
      method: 'POST',
      headers: useRequestHeaders(['cookie']),
      body: { response: attestation, nickname, challengeToken: res?.challengeToken },
    })
  }

  /**
   * Tap-only login. Resolves `{ needsUserName: true }` when the server wants a username first
   * (global allow-list disabled / too large); pass `userName` on the next call.
   */
  const loginWithKey = async (opts: { userName?: string; mobileWorker?: boolean } = {}): Promise<any> => {
    const res: any = await $fetch('/api/idempiere-auth/webauthn/login-options', {
      method: 'POST',
      headers: useRequestHeaders(['cookie']),
      body: { userName: opts.userName || undefined },
    })
    if (res?.needsUserName) return { needsUserName: true }

    const assertion = await getAssertion(pickOptions(res))
    const isPwa = typeof window !== 'undefined'
      && (window.matchMedia('(display-mode: standalone)').matches || !!(navigator as any)['standalone'])
    return await $fetch('/api/idempiere-auth/webauthn/login-verify', {
      method: 'POST',
      headers: useRequestHeaders(['cookie']),
      body: {
        response: assertion,
        challengeToken: res?.challengeToken,
        pwa: isPwa,
        mobileWorker: !!opts.mobileWorker,
      },
    })
  }

  const listKeys = async (forUserId?: number): Promise<{ keys: RegisteredKey[]; hasPasswordStored: boolean }> => {
    const url = forUserId ? `/api/admin/users/${forUserId}/webauthn` : '/api/idempiere-auth/webauthn/list'
    return await $fetch(url, { headers: useRequestHeaders(['cookie']) })
  }

  const deleteKey = async (credentialId: string, forUserId?: number) => {
    const url = forUserId ? `/api/admin/users/${forUserId}/webauthn/destroy` : '/api/idempiere-auth/webauthn/delete'
    return await $fetch(url, {
      method: 'DELETE',
      headers: useRequestHeaders(['cookie']),
      body: { credentialId },
    })
  }

  return { isSupported, isAvailable, capabilities, cancelNative, register, loginWithKey, listKeys, deleteKey }
}
