// Copyright (c) 2026 Sidero Labs, Inc.
//
// Use of this software is governed by the Business Source License
// included in the LICENSE file.
import { StorageSerializers, until, useLocalStorage } from '@vueuse/core'
import { useIDBKeyval } from '@vueuse/integrations/useIDBKeyval'
import { add, differenceInMilliseconds, formatRFC3339, isAfter } from 'date-fns'
import { watchEffect } from 'vue'
import { useRoute, useRouter } from 'vue-router'

import { b64Encode } from '@/api/fetch.pb'
import { AuthService } from '@/api/omni/auth/auth.pb'
import { AuthFlowQueryParam, FrontendAuthFlow, RedirectQueryParam } from '@/api/resources'

const { data: keyPair, isFinished: keyPairLoaded } = useIDBKeyval<CryptoKeyPair | null>(
  'keyPair',
  null,
  { writeDefaults: false },
)

const keyExpirationTime = useLocalStorage<Date>('keyExpirationTime', new Date(0), {
  serializer: StorageSerializers.date,
  writeDefaults: false,
})

const publicKeyID = useLocalStorage<string>('publicKeyID', '', {
  serializer: StorageSerializers.string,
  writeDefaults: false,
})

export function useKeys() {
  return {
    keyPair,
    keyExpirationTime,
    publicKeyID,
    clear() {
      keyPair.value = null
      keyExpirationTime.value = new Date(0)
      publicKeyID.value = ''
    },
    invalidate() {
      keyExpirationTime.value = new Date(0)
    },
  }
}

/**
 * We register this hook to watch the state of our keys
 * and to automatically send the user to the authentication page
 * when keys are expire.
 */
export function useWatchKeyExpiry() {
  const router = useRouter()
  const route = useRoute()
  const keys = useKeys()

  watchEffect(async (onCleanup) => {
    if (!keys.keyPair.value) return

    if (isAfter(Date.now(), keys.keyExpirationTime.value)) {
      return await clearKeysAndRedirect()
    }

    const keysReloadTimeout = window.setTimeout(
      clearKeysAndRedirect,
      differenceInMilliseconds(keys.keyExpirationTime.value, Date.now()),
    )

    onCleanup(() => clearTimeout(keysReloadTimeout))

    async function clearKeysAndRedirect() {
      keys.clear()

      try {
        /**
         * If we are here during initial page load,
         * the current route may not yet be initialised.
         */
        await router.isReady()
      } catch {
        // isReady will throw if interrupted by auth provider redirects
        return
      }

      /**
       * If we are already authenticating, do not replace the flow.
       * This can overwrite other flows like CLI / workload proxy.
       *
       * See: https://github.com/siderolabs/omni/issues/2121
       */
      if (route.name === 'Authenticate') return

      // Using location.replace instead of router.replace to do a full reload to clear any invalid states
      window.location.replace(
        router.resolve({
          name: 'Authenticate',
          query: { [AuthFlowQueryParam]: FrontendAuthFlow, [RedirectQueryParam]: route.fullPath },
        }).href,
      )
    }
  })
}

export async function signDetached(data: string, keyPair: CryptoKeyPair) {
  return await crypto.subtle.sign(
    { name: 'ECDSA', hash: 'SHA-256' },
    keyPair.privateKey,
    new TextEncoder().encode(data),
  )
}

export async function hasValidKeys() {
  // IndexedDB is async storage, and might not yet have been initialised
  if (!keyPairLoaded.value) await until(keyPairLoaded).toBe(true)

  if (!keyPair.value || !keyExpirationTime.value) return false

  return isAfter(keyExpirationTime.value, Date.now())
}

export async function createKeys(email: string) {
  email = email.toLowerCase()

  const keyPair = await crypto.subtle.generateKey({ name: 'ECDSA', namedCurve: 'P-256' }, false, [
    'sign',
    'verify',
  ])

  const buffer = await crypto.subtle.exportKey('spki', keyPair.publicKey)
  const array = new Uint8Array(buffer)
  const publicKeyB64 = b64Encode(array, 0, array.length)
    .match(/.{1,64}/g)
    ?.join('\n')

  const keyExpirationTime = add(new Date(), { hours: 7, minutes: 50 })

  const response = await AuthService.RegisterPublicKey({
    public_key: {
      plain_key: {
        key_pem: `-----BEGIN PUBLIC KEY-----\n${publicKeyB64}\n-----END PUBLIC KEY-----`,
        not_before: formatRFC3339(new Date()),
        not_after: formatRFC3339(keyExpirationTime),
      },
    },
    identity: { email },
  })

  return {
    keyPair,
    keyExpirationTime,
    publicKeyId: response.public_key_id!,
  }
}
