/**
 * A6 (FIX H-host-3) — leaf RENEWAL, UPGRADED off the DEV JSON-blob placeholder to REAL X.509.
 *
 * Renewal == re-issue a fresh short-TTL leaf for an ALREADY-bound, non-revoked identity using the
 * SAME registered key + the SAME subdomain the registry holds — NEVER client-supplied input (the
 * A4 anti-smuggling lesson). It does NOT re-implement issuance: it DELEGATES to the P-256 issuers
 * (`frpclient-issue` for the host frp-client leaf, `device-issue` for the device leaf), both of which
 * route through the single `assembleCertificate` primitive (sigAlg `ecdsa-p256`, the CA signer behind
 * KMS, INV9). Delegation keeps ONE registry-gate + ONE SAN grammar (DRY) and means the emitted cert is
 * a real, tool-parseable X.509 v3 leaf — not the retired signed-JSON placeholder.
 *
 * The gate work all lives in the delegated signers:
 *   - host:   `signHostLeaf(hostId, subjectPubkey, csr)` verifies CSR PoP, that the CSR's embedded key
 *             == `subjectPubkey` (the SAME-KEY check — the route passes the CURRENT cert's key), and
 *             that the registry host is active with a matching key; it stamps `host.subdomain`.
 *   - device: `signDeviceLeaf(deviceId, csr)` verifies CSR PoP, the device is active, and the CSR key
 *             == the registered key; it stamps `record.subdomainScope`.
 * Renewal adds the leaf's `notAfter`, read back from the emitted cert (byte-exact with the DER) for
 * BOTH paths so the `/renew` routes can echo it. It ALSO extends device validity: the host signer
 * recomputes `now()+ttl` on every call, but the device signer stamps the record's `notAfter` (set once
 * at enroll), so device renewal first bumps that record via the `DeviceRegistry` — otherwise every
 * device renewal would reproduce the original enrollment expiry.
 *
 * `reflect-metadata` must load before `@peculiar/x509` (tsyringe polyfill) — keep the side-effect first.
 */
import 'reflect-metadata'
import * as x509 from '@peculiar/x509'
import { webcrypto } from 'node:crypto'
import type { LeafSigner } from './sign.js'
import type { DeviceLeafSigner } from './device-issue.js'
import type { DeviceExpiryRenewer } from '../registry/devices.js'

x509.cryptoProvider.set(webcrypto)

/** A renewed leaf: the re-issued DER, its CA chain, and the parsed expiry `/renew` echoes back. */
export interface RenewedLeaf {
  readonly cert: Uint8Array
  readonly caChain: readonly Uint8Array[]
  readonly notAfter: Date
}

export interface LeafRenewerDeps {
  /** Host frp-client P-256 issuer (`frpclient-issue`). Routes through `assembleCertificate`. */
  readonly hostSigner: LeafSigner
  /** Device P-256 issuer (`device-issue`). Routes through `assembleCertificate`. */
  readonly deviceSigner: DeviceLeafSigner
  /**
   * Device leaf-expiry renewer (the `DeviceRegistry`). MUST share the SAME `DeviceStore` as
   * `deviceSigner` so the bumped expiry is visible to the signer's gate. The host path recomputes
   * `now()+ttl` inside its signer; the device signer reads `record.notAfter`, so device renewal must
   * bump that record FIRST or every renewal reproduces the original enrollment expiry.
   */
  readonly deviceExpiry: DeviceExpiryRenewer
}

export interface LeafRenewer {
  /**
   * Re-issue the host frp-client leaf. `subjectPubkey` is the CURRENT cert's SubjectPublicKeyInfo DER
   * (supplied by the mTLS-authenticated `/renew` route); passing it makes the delegated gate enforce
   * BOTH the SAME-KEY rule (CSR key == current cert key) AND registry consistency in one check. The
   * re-issued leaf's subdomain/SPIFFE come from the registry record — never from `csr` or the caller.
   */
  renewHostLeaf(hostId: string, subjectPubkey: Uint8Array, csr: Uint8Array): Promise<RenewedLeaf>
  /**
   * Re-issue the device leaf. The delegated gate enforces CSR PoP + active + CSR key == registered key;
   * the SAN is stamped from `record.subdomainScope` (SAME scope — no smuggling).
   */
  renewDeviceLeaf(deviceId: string, csr: Uint8Array): Promise<RenewedLeaf>
}

export function createLeafRenewer(deps: LeafRenewerDeps): LeafRenewer {
  return {
    async renewHostLeaf(hostId, subjectPubkey, csr) {
      // Delegated gate (frpclient-issue) rejects UNIFORMLY (LeafSignError) on any failure; issuance is
      // never reached. The re-issued leaf stamps host.subdomain from the registry (anti-smuggling).
      const { cert, caChain } = await deps.hostSigner.signHostLeaf(hostId, subjectPubkey, csr)
      const notAfter = new x509.X509Certificate(cert).notAfter // authoritative expiry, read from the cert
      return { cert, caChain, notAfter }
    },
    async renewDeviceLeaf(deviceId, csr) {
      // EXTEND validity FIRST: the device signer stamps `record.notAfter`, which is set ONCE at enroll,
      // so without this bump every renewal reproduces the original enrollment expiry (and eventually
      // issues already-expired certs). The registry recomputes + persists `now()+ttl` for an active
      // device, keeping the record (CRL/expiry pairing source of truth) consistent with the emitted
      // cert; an unknown/revoked device is left untouched and the delegated gate below rejects it.
      await deps.deviceExpiry.renewDeviceExpiry(deviceId)
      // Delegated gate (device-issue) rejects UNIFORMLY (DeviceLeafSignError) on any failure.
      const leaf = await deps.deviceSigner.signDeviceLeaf(deviceId, csr)
      // Defense in depth: surface the expiry actually embedded in the DER (byte-exact with the cert),
      // exactly as `renewHostLeaf` does — never a value that could drift from what was signed.
      const notAfter = new x509.X509Certificate(leaf.cert).notAfter
      return { cert: leaf.cert, caChain: leaf.caChain, notAfter }
    },
  }
}
