/**
 * Native-tunnel wiring E2E (fastify inject against the WIRED control-plane app from `main.ts`).
 *
 * Proves the full DEVICE path through the real composition root:
 *   mint a device:enroll token (auth/session, verified by the REAL §4.3 verifier the boot wires) →
 *   POST /device/enroll {csr:<real P-256 CSR>, subdomain:<owned>} → 201 with a cert that RE-PARSES,
 *   CHAINS to the wired dev device-CA, and carries dNSName <sub>.terminal.yaojia.wang → present that
 *   cert to POST /device/:id/renew → 201 fresh cert for the SAME subdomain.
 * Plus the A4 ownership gate end-to-end: enroll for an UNOWNED (and a foreign-owned) subdomain → 403.
 *
 * Also proves the HOST /renew path through the wired app: the initial frp-client leaf is minted via
 * the WIRED host signer (native host HTTP /enroll is a documented follow-up — the existing pairing
 * `/enroll` relay route is left untouched), then presented to /renew → 201 for the SAME subdomain.
 *
 * The verifier is the SAME seam the admin API uses (`boot/verifier.ts`), keyed off the boot env's
 * capability pubkey — so the enroll token is minted with the matching private key and travels the
 * exact verify path production uses.
 */
import 'reflect-metadata'
import { describe, test, expect, beforeEach } from 'vitest'
import * as x509 from '@peculiar/x509'
import { webcrypto } from 'node:crypto'
import type { FastifyInstance } from 'fastify'
import { generateEd25519KeyPair, exportEd25519PublicRaw } from 'relay-auth/src/crypto/ed25519.js'
import { createMemoryStores } from '../src/store/memory.js'
import { createHostRegistry } from '../src/registry/hosts.js'
import { createPairingIssuer } from '../src/pairing/issue.js'
import { fingerprint } from '../src/ca/fingerprint.js'
import { generateEd25519 } from '../src/util/crypto.js'
import { buildCsr } from '../src/ca/csr.js'
import { buildCsrEc } from '../src/ca/csr-ec.js'
import { buildControlPlane, type BuiltControlPlane } from '../src/main.js'
import { createCapabilityVerifier } from '../src/boot/verifier.js'
import { mintDeviceEnrollToken } from '../src/auth/session.js'
import { loadEnv, type ControlPlaneEnv } from '../src/env.js'
import { bytesToBase64 } from '../src/util/bytes.js'
import type { Stores } from '../src/store/ports.js'

x509.cryptoProvider.set(webcrypto)

const ACCOUNT_A = '11111111-1111-4111-8111-111111111111'
const ACCOUNT_B = '22222222-2222-4222-8222-222222222222'
const DNS_ZONE = 'terminal.yaojia.wang'

let signingKey: CryptoKey
let env: ControlPlaneEnv

beforeEach(async () => {
  // Ed25519 capability keypair: the raw public key becomes the boot env's verify key, so the
  // device:enroll token minted with the private key verifies through the wired real §4.3 verifier.
  const pair = await generateEd25519KeyPair()
  signingKey = pair.privateKey
  const rawPub = await exportEd25519PublicRaw(pair.publicKey)
  env = loadEnv({
    PG_URL: 'postgres://u:p@localhost:5432/cp',
    REDIS_URL: 'redis://localhost:6379',
    CAPABILITY_SIGN_PUBKEY_B64: bytesToBase64(rawPub),
    CA_INTERMEDIATE_KMS_KEY_REF: 'kms://key/intermediate',
    CA_INTERMEDIATE_CERT_PATH: '/etc/cp/int.pem',
    NODE_MTLS_TRUST_BUNDLE_PATH: '/etc/cp/node-ca.pem',
    RELAY_TRUST_DOMAIN: DNS_ZONE,
    BASE_DOMAIN: 'term.example.com',
  })
})

async function buildApp(): Promise<{ app: FastifyInstance; stores: Stores; built: BuiltControlPlane }> {
  const stores = createMemoryStores()
  const built = await buildControlPlane(env, { stores, verifier: createCapabilityVerifier() })
  await built.app.ready()
  return { app: built.app, stores, built }
}

/** Onboard a host so `accountId` OWNS `subdomain` in the ownership source of truth (registry). */
async function ownSubdomain(stores: Stores, accountId: string, subdomain: string): Promise<void> {
  const hosts = createHostRegistry({ hosts: stores.hosts })
  const { publicKeyRaw } = generateEd25519()
  await hosts.bindHost({ accountId, subdomain, agentPubkey: publicKeyRaw, enrollFpr: fingerprint(publicKeyRaw) })
}

/** Mint a single-use pairing code bound to `accountId` (the enroll gate reads accountId from the row). */
async function mintPairingCode(stores: Stores, accountId: string): Promise<string> {
  const issuer = createPairingIssuer({ pairing: stores.pairing, pairingTtlSec: 3600 })
  const { code } = await issuer.issuePairingCode(accountId)
  return code
}

/** A fresh P-256 host identity: its real PKCS#10 CSR + the SPKI DER the agent presents as agentPubkey. */
async function ecHostIdentity(subject = 'CN=web-terminal-host'): Promise<{ csr: Uint8Array; spki: Uint8Array }> {
  const { der: csr, keys } = await buildCsrEc(subject)
  const spki = new Uint8Array(await webcrypto.subtle.exportKey('spki', keys.publicKey))
  return { csr, spki }
}

/** Bind a host whose frp-client identity is a P-256 key (so the wired host signer can issue for it). */
async function bindP256Host(
  stores: Stores,
  accountId: string,
  subdomain: string,
): Promise<{ hostId: string; csr: Uint8Array; spki: Uint8Array }> {
  const hosts = createHostRegistry({ hosts: stores.hosts })
  const { der: csr, keys } = await buildCsrEc(`CN=${subdomain}`)
  const spki = new Uint8Array(await webcrypto.subtle.exportKey('spki', keys.publicKey))
  const host = await hosts.bindHost({ accountId, subdomain, agentPubkey: spki, enrollFpr: fingerprint(spki) })
  return { hostId: host.hostId, csr, spki }
}

function b64(bytes: Uint8Array): string {
  return Buffer.from(bytes).toString('base64')
}

function sanNames(der: Uint8Array): ReturnType<x509.GeneralNames['toJSON']> {
  const leaf = new x509.X509Certificate(der)
  return leaf.getExtension(x509.SubjectAlternativeNameExtension)!.names.toJSON()
}

describe('native-tunnel wiring — DEVICE e2e (enroll → renew)', () => {
  test('enroll an owned subdomain → 201 real leaf chaining to the dev device-CA → renew → 201 fresh cert, same subdomain', async () => {
    const { app, stores, built } = await buildApp()
    await ownSubdomain(stores, ACCOUNT_A, 'alice')
    const token = await mintDeviceEnrollToken(ACCOUNT_A, { signingKey })
    const { der: csr } = await buildCsrEc('CN=web-terminal-device')

    // ── ENROLL ───────────────────────────────────────────────────────────────────────────────────
    const enroll = await app.inject({
      method: 'POST',
      url: '/device/enroll',
      headers: { authorization: `Bearer ${token}` },
      payload: { csr: b64(csr), keyAlg: 'ec-p256', subdomain: 'alice', deviceName: 'iphone' },
    })
    expect(enroll.statusCode).toBe(201)
    const enrolled = JSON.parse(enroll.body)
    expect(typeof enrolled.deviceId).toBe('string')
    expect(enrolled.deviceId.length).toBeGreaterThan(0)

    // the returned cert re-parses as a real X.509 leaf …
    const leafDer = new Uint8Array(Buffer.from(enrolled.cert, 'base64'))
    const leaf = new x509.X509Certificate(leafDer)
    // … chains to the CA the app returned …
    const caDer = new Uint8Array(Buffer.from(enrolled.caChain[0], 'base64'))
    const caCert = new x509.X509Certificate(caDer)
    expect(await leaf.verify({ publicKey: caCert.publicKey, signatureOnly: true })).toBe(true)
    // … which IS the wired dev device-CA anchor …
    expect(b64(caDer)).toBe(b64(built.nativeTunnel.nativeCas.deviceCa.caCertDer))
    // … and carries the tenant-isolation dNSName SAN.
    expect(sanNames(leafDer)).toContainEqual({ type: 'dns', value: `alice.${DNS_ZONE}` })

    // ── RENEW (present the enroll-issued cert as the current mTLS client cert) ──────────────────────
    const renew = await app.inject({
      method: 'POST',
      url: `/device/${enrolled.deviceId}/renew`,
      headers: { 'x-client-cert': b64(leafDer) },
      payload: { csr: b64(csr) }, // same-key CSR
    })
    expect(renew.statusCode).toBe(201)
    const renewed = JSON.parse(renew.body)
    const renewedDer = new Uint8Array(Buffer.from(renewed.cert, 'base64'))
    expect(() => new x509.X509Certificate(renewedDer)).not.toThrow()
    // same subdomain — the renew stamps the registry scope, never client input.
    expect(sanNames(renewedDer)).toContainEqual({ type: 'dns', value: `alice.${DNS_ZONE}` })
    // and the fresh cert still chains to the device-CA anchor.
    expect(await new x509.X509Certificate(renewedDer).verify({ publicKey: caCert.publicKey, signatureOnly: true })).toBe(true)
  })

  test('enroll for an UNOWNED subdomain → 403, no cert issued (A4 ownership gate, end-to-end)', async () => {
    const { app, stores } = await buildApp()
    await ownSubdomain(stores, ACCOUNT_A, 'alice')
    const token = await mintDeviceEnrollToken(ACCOUNT_A, { signingKey })
    const { der: csr } = await buildCsrEc('CN=web-terminal-device')
    const res = await app.inject({
      method: 'POST',
      url: '/device/enroll',
      headers: { authorization: `Bearer ${token}` },
      payload: { csr: b64(csr), keyAlg: 'ec-p256', subdomain: 'charlie', deviceName: 'x' },
    })
    expect(res.statusCode).toBe(403)
    expect(JSON.parse(res.body).cert).toBeUndefined()
  })

  test('account B enrolling account A\'s subdomain → 403 (cross-tenant, no cert)', async () => {
    const { app, stores } = await buildApp()
    await ownSubdomain(stores, ACCOUNT_A, 'alice')
    const tokenB = await mintDeviceEnrollToken(ACCOUNT_B, { signingKey })
    const { der: csr } = await buildCsrEc('CN=web-terminal-device')
    const res = await app.inject({
      method: 'POST',
      url: '/device/enroll',
      headers: { authorization: `Bearer ${tokenB}` },
      payload: { csr: b64(csr), keyAlg: 'ec-p256', subdomain: 'alice', deviceName: 'x' },
    })
    expect(res.statusCode).toBe(403)
    expect(JSON.parse(res.body).cert).toBeUndefined()
  })

  test('missing enroll token → 401 (deny-by-default through the wired verifier seam)', async () => {
    const { app, stores } = await buildApp()
    await ownSubdomain(stores, ACCOUNT_A, 'alice')
    const { der: csr } = await buildCsrEc('CN=web-terminal-device')
    const res = await app.inject({
      method: 'POST',
      url: '/device/enroll',
      payload: { csr: b64(csr), keyAlg: 'ec-p256', subdomain: 'alice', deviceName: 'x' },
    })
    expect(res.statusCode).toBe(401)
  })
})

describe('native-tunnel wiring — HOST /renew e2e', () => {
  test('initial frp-client leaf (wired host signer) → /renew → 201 fresh cert, SAME subdomain, chains to frp-client-CA', async () => {
    const { app, stores, built } = await buildApp()
    const { hostId, csr, spki } = await bindP256Host(stores, ACCOUNT_A, 'alice')
    // Initial leaf, minted by the WIRED host signer (chains to the wired frp-client-CA anchor).
    const { cert: currentCert } = await built.nativeTunnel.hostSigner.signHostLeaf(hostId, spki, csr)

    const res = await app.inject({
      method: 'POST',
      url: '/renew',
      headers: { 'x-client-cert': b64(currentCert) },
      payload: { csr: b64(csr) },
    })
    expect(res.statusCode).toBe(201)
    const renewedDer = new Uint8Array(Buffer.from(JSON.parse(res.body).cert, 'base64'))
    expect(sanNames(renewedDer)).toContainEqual({ type: 'dns', value: `alice.${DNS_ZONE}` })
    const caCert = new x509.X509Certificate(built.nativeTunnel.nativeCas.frpClientCa.caCertDer)
    expect(await new x509.X509Certificate(renewedDer).verify({ publicKey: caCert.publicKey, signatureOnly: true })).toBe(true)
  })

  test('a current cert from an UNTRUSTED (different-instance) frp-client-CA is rejected by the wired anchor set → 401', async () => {
    const { app } = await buildApp()
    // A second, independent wired control-plane = a DIFFERENT (untrusted) frp-client-CA. Bind the same
    // host+identity there and mint a leaf under ITS CA, then present it to the REAL app's /renew.
    const rogueStores = createMemoryStores()
    const rogue = await buildControlPlane(env, { stores: rogueStores, verifier: createCapabilityVerifier() })
    const rogueBound = await bindP256Host(rogueStores, ACCOUNT_A, 'alice')
    const { cert: rogueCert } = await rogue.nativeTunnel.hostSigner.signHostLeaf(
      rogueBound.hostId,
      rogueBound.spki,
      rogueBound.csr,
    )
    const res = await app.inject({
      method: 'POST',
      url: '/renew',
      headers: { 'x-client-cert': b64(rogueCert) },
      payload: { csr: b64(rogueBound.csr) },
    })
    expect(res.statusCode).toBe(401)
  })
})

describe('native-tunnel wiring — HOST /enroll HTTP arm (P-256 frp-client leaf)', () => {
  test('full host onboard: mint code → POST /enroll (EC CSR) → 201 frp-client leaf (chains to frp-client-CA + dNSName SAN, hostContentSecret null) → /renew → 201 SAME subdomain', async () => {
    const { app, stores, built } = await buildApp()
    const code = await mintPairingCode(stores, ACCOUNT_A)
    const { csr, spki } = await ecHostIdentity()

    // ── ENROLL (native P-256 arm — routed by CSR key algorithm) ────────────────────────────────────
    const enroll = await app.inject({
      method: 'POST',
      url: '/enroll',
      payload: { code, machineId: 'MB-A1B2C3', agentPubkey: b64(spki), csr: b64(csr) },
    })
    expect(enroll.statusCode).toBe(201)
    const body = JSON.parse(enroll.body)
    expect(typeof body.hostId).toBe('string')
    expect(body.hostId.length).toBeGreaterThan(0)
    expect(typeof body.subdomain).toBe('string')
    expect(body.subdomain.length).toBeGreaterThan(0)
    // native tunnel has no E2E-relay content secret (L-host-hcs).
    expect(body.hostContentSecret).toBeNull()

    // the returned cert re-parses as a real X.509 leaf …
    const leafDer = new Uint8Array(Buffer.from(body.cert, 'base64'))
    const leaf = new x509.X509Certificate(leafDer)
    // … chains to the wired frp-client-CA anchor …
    const caCert = new x509.X509Certificate(built.nativeTunnel.nativeCas.frpClientCa.caCertDer)
    expect(await leaf.verify({ publicKey: caCert.publicKey, signatureOnly: true })).toBe(true)
    const caDer = new Uint8Array(Buffer.from(body.caChain[0], 'base64'))
    expect(b64(caDer)).toBe(b64(built.nativeTunnel.nativeCas.frpClientCa.caCertDer))
    // … and carries the AUTHORITATIVE (server-assigned) dNSName SAN <sub>.<zone>.
    expect(sanNames(leafDer)).toContainEqual({ type: 'dns', value: `${body.subdomain}.${DNS_ZONE}` })

    // the host binding persisted the SAME authoritative subdomain (not client input).
    const host = await stores.hosts.get(body.hostId)
    expect(host?.subdomain).toBe(body.subdomain)
    expect(host?.accountId).toBe(ACCOUNT_A)

    // ── RENEW (present the enroll-issued cert as the current mTLS client cert) ──────────────────────
    const renew = await app.inject({
      method: 'POST',
      url: '/renew',
      headers: { 'x-client-cert': b64(leafDer) },
      payload: { csr: b64(csr) }, // same-key CSR
    })
    expect(renew.statusCode).toBe(201)
    const renewedDer = new Uint8Array(Buffer.from(JSON.parse(renew.body).cert, 'base64'))
    // the renew stamps the registry scope — SAME subdomain, never client input.
    expect(sanNames(renewedDer)).toContainEqual({ type: 'dns', value: `${body.subdomain}.${DNS_ZONE}` })
    expect(await new x509.X509Certificate(renewedDer).verify({ publicKey: caCert.publicKey, signatureOnly: true })).toBe(true)
  })

  test('anti-smuggling: a client body requesting a DIFFERENT subdomain does NOT change the issued SAN', async () => {
    const { app, stores } = await buildApp()
    const code = await mintPairingCode(stores, ACCOUNT_A)
    const { csr, spki } = await ecHostIdentity()

    const enroll = await app.inject({
      method: 'POST',
      url: '/enroll',
      // the attacker tries to steer the SAN to a subdomain it was never assigned:
      payload: { code, machineId: 'x', agentPubkey: b64(spki), csr: b64(csr), subdomain: 'victim' },
    })
    expect(enroll.statusCode).toBe(201)
    const body = JSON.parse(enroll.body)
    // the SAN is the SERVER-assigned subdomain, never the client-supplied 'victim'.
    expect(body.subdomain).not.toBe('victim')
    const leafDer = new Uint8Array(Buffer.from(body.cert, 'base64'))
    expect(sanNames(leafDer)).toContainEqual({ type: 'dns', value: `${body.subdomain}.${DNS_ZONE}` })
    expect(sanNames(leafDer)).not.toContainEqual({ type: 'dns', value: `victim.${DNS_ZONE}` })
    // and the host registry bound the server-assigned subdomain, not 'victim'.
    const host = await stores.hosts.get(body.hostId)
    expect(host?.subdomain).toBe(body.subdomain)
    expect(host?.subdomain).not.toBe('victim')
  })

  test('single-use: replaying an already-redeemed pairing code → NOT 201 (double-spend guard)', async () => {
    const { app, stores } = await buildApp()
    const code = await mintPairingCode(stores, ACCOUNT_A)
    const first = await ecHostIdentity('CN=host-1')
    const ok = await app.inject({
      method: 'POST',
      url: '/enroll',
      payload: { code, machineId: 'a', agentPubkey: b64(first.spki), csr: b64(first.csr) },
    })
    expect(ok.statusCode).toBe(201)

    const second = await ecHostIdentity('CN=host-2')
    const replay = await app.inject({
      method: 'POST',
      url: '/enroll',
      payload: { code, machineId: 'a', agentPubkey: b64(second.spki), csr: b64(second.csr) },
    })
    expect(replay.statusCode).not.toBe(201)
  })

  test('no-substitution: agentPubkey that does not match the CSR key → NOT 201', async () => {
    const { app, stores } = await buildApp()
    const code = await mintPairingCode(stores, ACCOUNT_A)
    const { csr } = await ecHostIdentity('CN=host-real')
    const other = await ecHostIdentity('CN=host-other')
    const res = await app.inject({
      method: 'POST',
      url: '/enroll',
      // present a DIFFERENT key than the CSR embeds — the gate's no-substitution check must reject.
      payload: { code, machineId: 'a', agentPubkey: b64(other.spki), csr: b64(csr) },
    })
    expect(res.statusCode).not.toBe(201)
  })

  test('non-regression: an Ed25519 relay /enroll still issues the PEM relay leaf + wrapped secret (native arm does not hijack it)', async () => {
    const { app, stores } = await buildApp()
    const code = await mintPairingCode(stores, ACCOUNT_A)
    const { publicKeyRaw, privateKey } = generateEd25519()
    const res = await app.inject({
      method: 'POST',
      url: '/enroll',
      payload: { code, agentPubkey: bytesToBase64(publicKeyRaw), csr: bytesToBase64(buildCsr(privateKey, publicKeyRaw)) },
    })
    expect(res.statusCode).toBe(201)
    const body = JSON.parse(res.body)
    expect(body.cert).toContain('BEGIN CERTIFICATE') // PEM relay leaf, unchanged
    expect(typeof body.hostContentSecret).toBe('string') // base64url wrapped secret, NOT null
  })
})
