/**
 * P5 signing-key setup + capability-token / DPoP bundle builder for Phase 0. Wires the REAL
 * relay-auth issue + DPoP primitives (deep-imported test/internal helpers — relay-auth has no
 * `exports` map, so subpath imports resolve through the relay-run node_modules symlink). Lifted
 * from `e2e/harness/dpop.ts`.
 *
 * The DPoP `htu`/`htm` are LOAD-BEARING: the browser signs them into the proof and the relay
 * re-derives them from the request's own authority (never from client claims). `htuFor` is the
 * SINGLE source of truth used by BOTH sides so issuance and verification agree.
 */
import { randomUUID } from 'node:crypto'
import { issueCapabilityToken, type DpopContext } from 'relay-auth'
import type { CapabilityRight } from 'relay-contracts'
import {
  configureVerifyKey,
  resetVerifyKeyForTest,
} from 'relay-auth/src/config/keys.js'
import {
  generateEd25519KeyPair,
  exportEd25519PublicRaw,
} from 'relay-auth/src/crypto/ed25519.js'
import { buildDpopProof, resetDpopCacheForTest } from 'relay-auth/src/capability/verify.js'
import { jwkThumbprint } from 'relay-auth/src/crypto/thumbprint.js'
import { principal } from './memory-stores.js'

export { resetDpopCacheForTest }

/** DPoP HTTP method the browser binds into every proof (WS upgrade is a GET). */
export const DPOP_HTM = 'GET' as const

/**
 * Canonical DPoP `htu` for a subdomain/aud. The relay derives this from the resolved subdomain
 * (its own authority) and the browser signs the identical value — the ONE place the string is
 * defined, so the two never drift.
 */
export function htuFor(aud: string): string {
  return `https://${aud}/ws`
}

export interface P5Key {
  readonly signingKey: CryptoKey
  readonly publicRaw: Uint8Array
}

/** Configure the P5 verifying key globally and return the matching private signing key. */
export async function setupP5SigningKey(): Promise<P5Key> {
  resetVerifyKeyForTest()
  const pair = await generateEd25519KeyPair()
  configureVerifyKey(pair.publicKey)
  const publicRaw = await exportEd25519PublicRaw(pair.publicKey)
  return { signingKey: pair.privateKey, publicRaw }
}

interface Ephemeral {
  readonly privateKey: CryptoKey
  readonly publicRaw: Uint8Array
}

async function makeEphemeral(): Promise<Ephemeral> {
  const pair = await generateEd25519KeyPair()
  return { privateKey: pair.privateKey, publicRaw: await exportEd25519PublicRaw(pair.publicKey) }
}

export interface IssueOpts {
  readonly accountId: string
  readonly host: string
  readonly aud: string
  readonly rights?: readonly CapabilityRight[]
  readonly ttl?: number
  readonly now: number
}

/**
 * A capability token bundled with a matching DPoP proof. `newDpop(at?)` mints a FRESH DPoP proof
 * (new jti) bound to the SAME ephemeral key — needed to re-present the same token under a distinct
 * DPoP without tripping the DPoP replay cache.
 */
export interface CapBundle {
  readonly raw: string
  readonly dpop: DpopContext
  readonly newDpop: (at?: number) => Promise<DpopContext>
}

export async function issueCapBundle(signingKey: CryptoKey, o: IssueOpts): Promise<CapBundle> {
  const eph = await makeEphemeral()
  const cnfJkt = await jwkThumbprint(eph.publicRaw)
  const raw = await issueCapabilityToken(
    {
      principal: principal(o.accountId),
      aud: o.aud,
      host: o.host,
      rights: o.rights ?? ['attach'],
      ttlSeconds: o.ttl ?? 45,
      cnfJkt,
    },
    signingKey,
    o.now,
  )
  const htu = htuFor(o.aud)
  const newDpop = async (at: number = o.now): Promise<DpopContext> => ({
    proofJws: await buildDpopProof(eph.privateKey, eph.publicRaw, {
      htu,
      htm: DPOP_HTM,
      jti: randomUUID(),
      iat: at,
    }),
    htu,
    htm: DPOP_HTM,
  })
  return { raw, dpop: await newDpop(o.now), newDpop }
}
