/**
 * B1 / FIX H-host-2, H-host-4 — the P-256 HOST frp-client leaf signer.
 *
 * The `frp-client-CA` is P-256 (matching the VPS `gen-device-ca.sh` "P-256 never Ed25519" and the
 * frps Go-TLS client-cert requirement), so the host key, its CSR, and this leaf are ALL P-256 —
 * distinct from the Ed25519 relay agent-CA. This module mirrors `ca/issue.ts`'s
 * `createRealLeafSigner` SHAPE (registry-gate → build extensions → sign → return leaf + CA chain)
 * but:
 *   (a) verifies the P-256 CSR via `verifyCsrPoPEc` (NOT the Ed25519 `verifyCsrPoP`);
 *   (b) gates on the host registry (bound, active, non-revoked, embedded pubkey == registered
 *       pubkey — the pubkey is an EC SubjectPublicKeyInfo DER here, not a raw-32 Ed25519 key);
 *   (c) issues via `assembleCertificate` (A1) with `sigAlg:'ecdsa-p256'` and the frp-client-CA
 *       `CaSigner` — raw CA key never loaded (INV9).
 *
 * SAN grammar (ONE grammar, FIX H-host-4): `dNSName <sub>.<dnsZone>` is the ENFORCEMENT key the
 * A3 nginx njs parses; the `URI` SPIFFE-ID (`.../host/<sub>`, built with relay-auth's own builder so
 * it can never drift from the verifier's parser) is identity/audit. EKU clientAuth, CA:false,
 * KeyUsage digitalSignature; validity `[now-CLOCK_SKEW, now+ttl]`.
 *
 * `reflect-metadata` must load before `@peculiar/x509` (tsyringe polyfill) — keep it first.
 */
import 'reflect-metadata'
import * as x509 from '@peculiar/x509'
import { webcrypto, randomBytes } from 'node:crypto'
import { spiffeIdFor } from 'relay-auth/src/agent/spiffe.js'
import type { HostStore } from '../store/ports.js'
import type { HostRecord } from '../model/records.js'
import type { CaSigner } from '../boot/ca-wiring.js'
import { assembleCertificate } from './x509-assembler.js'
import { verifyCsrPoPEc } from './csr-ec.js'
import { LeafSignError, DEFAULT_LEAF_TTL_SEC, type LeafSigner } from './sign.js'
import { timingSafeEqualBytes } from '../util/bytes.js'

x509.cryptoProvider.set(webcrypto)

/** Backdate notBefore to tolerate small clock skew between control-plane, host, frps and nginx. */
const CLOCK_SKEW_SEC = 60
/** DNS zone the reachable subdomain lives under; the leftmost label is the nginx enforcement key. */
const DEFAULT_DNS_ZONE = 'terminal.yaojia.wang'

export interface FrpClientLeafSignerDeps {
  readonly hosts: HostStore
  /** frp-client-CA P-256 signer (KMS-shaped; `inProcessP256CaSigner` in tests). Raw key never loaded. */
  readonly signer: CaSigner
  /** frp-client-CA subject as a `Name` — used verbatim as the leaf issuer so `checkIssued` matches. */
  readonly issuerName: x509.Name | string
  /** DER of [frp-client-CA] (+ any parents) returned to the host as its CA bundle. */
  readonly caChainDer: readonly Uint8Array[]
  /** Bare trust domain for the SPIFFE-ID; `spiffeIdFor` prepends `relay.`. */
  readonly trustDomain: string
  /** DNS zone for the enforcement `dNSName` (default `terminal.yaojia.wang`). */
  readonly dnsZone?: string
  readonly leafTtlSec?: number
}

/**
 * P-256 host-leaf gate — the ECDSA analogue of `sign.ts` `assertLeafGate`. SAME ordered checks,
 * SAME uniform `LeafSignError` reject (never leaks which check failed), issuance NEVER reached on
 * any failure:
 *   1. P-256 CSR proof-of-possession (`verifyCsrPoPEc`, independent of registry state);
 *   2. no substitution: the CSR's embedded EC SPKI == the presented `agentPubkey`;
 *   3. registry: host bound, ACTIVE (non-revoked), and its stored pubkey == the presented one.
 */
async function assertFrpClientLeafGate(
  hosts: HostStore,
  hostId: string,
  agentPubkey: Uint8Array,
  csr: Uint8Array,
): Promise<HostRecord> {
  const pop = await verifyCsrPoPEc(csr)
  if (!pop.ok) throw new LeafSignError('csr_rejected')
  if (!timingSafeEqualBytes(pop.embeddedPubSpki, agentPubkey)) throw new LeafSignError('csr_rejected')
  const host = await hosts.get(hostId)
  if (host === null || host.status === 'revoked') throw new LeafSignError('not_registered')
  if (!timingSafeEqualBytes(host.agentPubkey, agentPubkey)) throw new LeafSignError('not_registered')
  return host
}

/**
 * Build the production HOST frp-client leaf signer. Every issued leaf: X.509 v3, EC P-256 subject =
 * the enrolled host pubkey (SPKI DER), SAN = { dNSName `<sub>.<zone>`, URI host-SPIFFE-ID }, CA:false,
 * KeyUsage digitalSignature, EKU clientAuth, validity `[now-skew, now+ttl]`, signed by the P-256
 * frp-client-CA via `assembleCertificate`. Returns leaf DER + the injected CA chain DER.
 */
export function createFrpClientLeafSigner(deps: FrpClientLeafSignerDeps): LeafSigner {
  const ttl = deps.leafTtlSec ?? DEFAULT_LEAF_TTL_SEC
  const dnsZone = deps.dnsZone ?? DEFAULT_DNS_ZONE
  return {
    async signHostLeaf(hostId, agentPubkey, csr) {
      const host = await assertFrpClientLeafGate(deps.hosts, hostId, agentPubkey, csr)
      const sub = host.subdomain
      const dnsName = `${sub}.${dnsZone}`
      const spiffe = spiffeIdFor(host.accountId, sub, deps.trustDomain, 'host')
      const now = Date.now()
      const cert = await assembleCertificate({
        subjectPublicKey: agentPubkey, // EC P-256 SubjectPublicKeyInfo DER
        subject: `CN=${sub}`,
        issuer: deps.issuerName,
        serialNumber: randomBytes(16),
        notBefore: new Date(now - CLOCK_SKEW_SEC * 1000),
        notAfter: new Date(now + ttl * 1000),
        extensions: [
          new x509.SubjectAlternativeNameExtension([
            { type: 'dns', value: dnsName }, // FIX H-host-4: the enforcement key nginx njs parses
            { type: 'url', value: spiffe }, // identity/audit — never the enforcement key
          ]),
          new x509.BasicConstraintsExtension(false, undefined, true),
          new x509.KeyUsagesExtension(x509.KeyUsageFlags.digitalSignature, true),
          new x509.ExtendedKeyUsageExtension([x509.ExtendedKeyUsage.clientAuth]),
        ],
        signer: deps.signer,
        sigAlg: 'ecdsa-p256',
      })
      return { cert, caChain: deps.caChainDer }
    },
  }
}
