/**
 * Native host frp-client enrollment — the EC-P256 arm of `POST /enroll` (routed by CSR key algorithm
 * in api/provision.ts). Reuses the SHARED pairing gate (`gateAndConsumePairingCode`: single-use CAS +
 * code-scoped lockout + expiry + PoP/no-substitution) from redeem.ts with an EC-P256 verifier, then:
 *   1. assigns an AUTHORITATIVE server-side subdomain (`SubdomainAssigner`), NEVER client input;
 *   2. binds the host in the ownership registry (accountId from the pairing ROW, INV3);
 *   3. issues a P-256 frp-client leaf via the wired `createFrpClientLeafSigner` — the signer stamps
 *      the dNSName SAN from `host.subdomain` (the label bound in step 1), so a client can NEVER steer
 *      the SAN to a subdomain it was not assigned (A4 anti-smuggling isolation lesson).
 *
 * Native tunnel has NO E2E-relay content secret (L-host-hcs); the route returns `hostContentSecret:
 * null`. This module returns raw leaf/CA DER; the route base64-encodes them.
 *
 * M-cp-idempotent (DEFERRED — documented): the frozen `HostRecordSchema` (relay-contracts, `.strict()`)
 * has no `machineId` column, so machineId-keyed dedup (reinstall → SAME subdomain) is NOT implemented
 * here — there is nowhere authoritative to persist it for lookup. A reinstall redeems a fresh pairing
 * code and receives a fresh subdomain. `machineId` is accepted at the boundary and audited only.
 */
import type { PairingStore } from '../store/ports.js'
import type { HostRegistry } from '../registry/hosts.js'
import type { SubdomainAssigner } from '../subdomain/assign.js'
import type { LeafSigner } from '../ca/sign.js'
import type { AuditWriter } from '../audit/log.js'
import { noopAuditWriter } from '../audit/log.js'
import { gateAndConsumePairingCode, type CsrPopVerifier } from './redeem.js'
import { verifyCsrPoPEc } from '../ca/csr-ec.js'
import { fingerprint } from '../ca/fingerprint.js'
import { nowIso } from '../util/ids.js'

/** `POST /enroll` native (EC-P256) body, post-boundary-validation. `machineId` is accepted but unused (see header). */
export interface NativeHostEnrollInput {
  readonly code: string
  /** EC P-256 SubjectPublicKeyInfo DER (NOT a raw-32 Ed25519 key). */
  readonly agentPubkey: Uint8Array
  /** PKCS#10 CSR DER (P-256). */
  readonly csr: Uint8Array
  readonly machineId?: string
}

/** Raw issuance output — the route base64-encodes `cert`/`caChain` and appends `hostContentSecret: null`. */
export interface NativeEnrollResult {
  readonly hostId: string
  readonly subdomain: string
  /** frp-client leaf DER. */
  readonly cert: Uint8Array
  /** frp-client-CA chain DER (the host's trust bundle). */
  readonly caChain: readonly Uint8Array[]
}

export interface NativeHostEnrollerDeps {
  readonly pairing: PairingStore
  readonly hosts: HostRegistry
  readonly subdomains: SubdomainAssigner
  /** The wired P-256 frp-client leaf signer (`createFrpClientLeafSigner`). */
  readonly hostSigner: LeafSigner
  readonly pairingMaxRedeemAttempts: number
  readonly audit?: AuditWriter
}

export interface NativeHostEnroller {
  enrollNativeHost(input: NativeHostEnrollInput): Promise<NativeEnrollResult>
}

/** Adapt the EC-P256 PoP verifier to the shared gate's `{ ok, embeddedPub }` shape. */
const ecCsrVerifier: CsrPopVerifier = async (csr) => {
  const result = await verifyCsrPoPEc(csr)
  return { ok: result.ok, embeddedPub: result.embeddedPubSpki }
}

/**
 * Build the native host enroller. Every successful enroll: consumes the pairing code once, binds the
 * host under a SERVER-assigned subdomain, and mints a P-256 frp-client leaf whose dNSName SAN is that
 * same authoritative subdomain (never client input).
 */
export function createNativeHostEnroller(deps: NativeHostEnrollerDeps): NativeHostEnroller {
  const audit = deps.audit ?? noopAuditWriter()
  return {
    async enrollNativeHost(input) {
      const { accountId } = await gateAndConsumePairingCode(
        { pairing: deps.pairing, pairingMaxRedeemAttempts: deps.pairingMaxRedeemAttempts },
        { code: input.code, agentPubkey: input.agentPubkey, csr: input.csr },
        ecCsrVerifier,
      )
      // AUTHORITATIVE subdomain — assigned server-side from the account, never a request field (A4).
      const subdomain = await deps.subdomains.assignSubdomain(accountId)
      const host = await deps.hosts.bindHost({
        accountId,
        subdomain,
        agentPubkey: input.agentPubkey,
        enrollFpr: fingerprint(input.agentPubkey),
      })
      // The signer reads `host.subdomain` for the dNSName SAN — the SAME label bound above.
      const leaf = await deps.hostSigner.signHostLeaf(host.hostId, input.agentPubkey, input.csr)
      // Same `pairing.redeem` audit action as the relay arm (native IS a pairing redemption); the
      // `arm: 'native'` meta discriminates the two without extending the frozen AuditAction enum.
      await audit.writeAuditEvent({
        action: 'pairing.redeem',
        principalId: `host:${host.hostId}`,
        accountId,
        hostId: host.hostId,
        ts: nowIso(),
        meta:
          input.machineId !== undefined
            ? { subdomain, arm: 'native', machineId: input.machineId }
            : { subdomain, arm: 'native' },
      })
      return { hostId: host.hostId, subdomain, cert: leaf.cert, caChain: leaf.caChain }
    },
  }
}
