/**
 * A3 (FIX C-native-3) — the SINGLE load-bearing tenant-isolation control, unit-tested under Node.
 *
 * `deploy/nginx/njs/getCertSub.js` runs inside nginx (njs) via `js_set $cert_sub getCertSub`. This
 * suite exercises the SAME source under Node against REAL P-256 leaf certs minted by the A1 issuance
 * primitive (`assembleCertificate` + `inProcessP256CaSigner`) — the exact structure the B1/A4 issuers
 * stamp — so a parsing regression is caught in CI, not in production where it would be a skeleton-key.
 *
 * Two layers, both here:
 *  1. `extractLeftmostDnsLabel` — the njs SAN extractor: leftmost dNSName label, fail-closed to ''.
 *  2. `certHostOk` — a JS mirror of the nginx `map "$cert_sub:$ssl_server_name"` PCRE rule, so the
 *     allow/deny semantics (incl. the cross-tenant NEGATIVE case) are asserted deterministically.
 */
import 'reflect-metadata'
import { describe, test, expect } from 'vitest'
import * as x509 from '@peculiar/x509'
import { webcrypto, randomBytes } from 'node:crypto'
import certsub from '../../deploy/nginx/njs/getCertSub.js'
import { assembleCertificate } from '../src/ca/x509-assembler.js'
import { inProcessP256CaSigner } from '../src/boot/ca-wiring.js'

x509.cryptoProvider.set(webcrypto)

const { extractLeftmostDnsLabel, getCertSub } = certsub

const DAY_MS = 24 * 60 * 60 * 1000

/** WebCrypto key pair (the `CryptoKeyPair` global is not in this project's TS lib set). */
type KeyPair = { readonly publicKey: CryptoKey; readonly privateKey: CryptoKey }

async function genP256(): Promise<KeyPair> {
  return (await webcrypto.subtle.generateKey(
    { name: 'ECDSA', namedCurve: 'P-256' },
    true,
    ['sign', 'verify'],
  )) as unknown as KeyPair
}

function derToPem(der: Uint8Array, label = 'CERTIFICATE'): string {
  const b64 = Buffer.from(der).toString('base64').match(/.{1,64}/g)?.join('\n') ?? ''
  return `-----BEGIN ${label}-----\n${b64}\n-----END ${label}-----\n`
}

type SanEntry = { readonly type: 'dns' | 'url'; readonly value: string }

/**
 * Mint a REAL P-256 leaf PEM off the dev P-256 CA (stand-in for the B1/A4 issuers), carrying exactly
 * the supplied SAN entries. Empty `san` omits the SAN extension entirely (the no-SAN case).
 */
async function mintLeafPem(san: readonly SanEntry[]): Promise<string> {
  const subject = await genP256()
  const extensions: x509.Extension[] = []
  if (san.length > 0) {
    extensions.push(new x509.SubjectAlternativeNameExtension(san as { type: string; value: string }[]))
  }
  extensions.push(new x509.BasicConstraintsExtension(false, undefined, true))
  extensions.push(new x509.ExtendedKeyUsageExtension([x509.ExtendedKeyUsage.clientAuth]))
  const now = Date.now()
  const der = await assembleCertificate({
    subjectPublicKey: subject.publicKey,
    subject: 'CN=leaf',
    issuer: 'CN=device-CA',
    serialNumber: randomBytes(16),
    notBefore: new Date(now - 60_000),
    notAfter: new Date(now + DAY_MS),
    extensions,
    signer: inProcessP256CaSigner(),
    sigAlg: 'ecdsa-p256',
  })
  return derToPem(der)
}

const SPIFFE_ALICE = 'spiffe://terminal.yaojia.wang/account/a1/host/alice'

describe('extractLeftmostDnsLabel — POSITIVE (real A1-minted leaves, FIX C-native-3)', () => {
  test("SAN dNSName 'alice.terminal.yaojia.wang' (+ spiffe URI) → 'alice'", async () => {
    const pem = await mintLeafPem([
      { type: 'dns', value: 'alice.terminal.yaojia.wang' },
      { type: 'url', value: SPIFFE_ALICE },
    ])
    expect(extractLeftmostDnsLabel(pem)).toBe('alice')
  })

  test("SAN dNSName 'bob.terminal.yaojia.wang' → 'bob'", async () => {
    const pem = await mintLeafPem([{ type: 'dns', value: 'bob.terminal.yaojia.wang' }])
    expect(extractLeftmostDnsLabel(pem)).toBe('bob')
  })

  test('picks the dNSName even when a URI SAN is listed FIRST (order-independent)', async () => {
    const pem = await mintLeafPem([
      { type: 'url', value: 'spiffe://terminal.yaojia.wang/account/a1/host/charlie' },
      { type: 'dns', value: 'charlie.terminal.yaojia.wang' },
    ])
    expect(extractLeftmostDnsLabel(pem)).toBe('charlie')
  })

  test('takes the FIRST dNSName when multiple are present', async () => {
    const pem = await mintLeafPem([
      { type: 'dns', value: 'dave.terminal.yaojia.wang' },
      { type: 'dns', value: 'erin.terminal.yaojia.wang' },
    ])
    expect(extractLeftmostDnsLabel(pem)).toBe('dave')
  })
})

describe('extractLeftmostDnsLabel — FAIL-CLOSED (any parse failure / missing SAN → "")', () => {
  test('a leaf with ONLY a URI SAN (no dNSName) → ""', async () => {
    const pem = await mintLeafPem([{ type: 'url', value: SPIFFE_ALICE }])
    expect(extractLeftmostDnsLabel(pem)).toBe('')
  })

  test('a leaf with NO SAN extension at all → ""', async () => {
    const pem = await mintLeafPem([])
    expect(extractLeftmostDnsLabel(pem)).toBe('')
  })

  test('empty string → ""', () => {
    expect(extractLeftmostDnsLabel('')).toBe('')
  })

  test('non-PEM garbage → ""', () => {
    expect(extractLeftmostDnsLabel('this is not a certificate')).toBe('')
  })

  test('valid PEM markers but garbage base64 body → ""', () => {
    const junk = '-----BEGIN CERTIFICATE-----\nZ m 9 v Y m F y\n-----END CERTIFICATE-----\n'
    expect(extractLeftmostDnsLabel(junk)).toBe('')
  })

  test('truncated DER inside valid PEM markers → ""', async () => {
    const pem = await mintLeafPem([{ type: 'dns', value: 'alice.terminal.yaojia.wang' }])
    const body = pem.split('\n').slice(1, 3).join('') // keep only the first ~2 base64 lines
    const truncated = `-----BEGIN CERTIFICATE-----\n${body}\n-----END CERTIFICATE-----\n`
    expect(extractLeftmostDnsLabel(truncated)).toBe('')
  })

  test('CP5: a nested-length-inconsistent cert cannot leak a dNSName past its parent bound → ""', () => {
    // Hand-crafted DER: Certificate→TBS→[3]→Extensions→Extension(SAN)→extnValue OCTET STRING whose
    // GeneralNames SEQUENCE declares length 6 — overrunning the 2-byte extnValue content — so its
    // "content" reaches into a dNSName ('evil') that actually sits AFTER the OCTET STRING as a sibling
    // (but still inside the buffer). Without the parent-bound check readTlv would accept the inflated
    // GeneralNames and return 'evil' (a cross-tenant skeleton-key); bounding each child to its parent's
    // contentEnd makes the extractor fail-closed to ''.
    const der = [
      0x30, 0x17, //   Certificate SEQUENCE, len 23
      0x30, 0x15, //     TBSCertificate SEQUENCE, len 21
      0xa3, 0x13, //       [3] extensions container, len 19
      0x30, 0x11, //         Extensions SEQUENCE, len 17
      0x30, 0x09, //           Extension SEQUENCE, len 9
      0x06, 0x03, 0x55, 0x1d, 0x11, //   OID 2.5.29.17 (subjectAltName)
      0x04, 0x02, 0x30, 0x06, //         extnValue OCTET STRING (len 2) = GeneralNames header claiming len 6
      0x82, 0x04, 0x65, 0x76, 0x69, 0x6c, // dNSName [2] "evil" — sibling, reached only by the lie
    ]
    const b64 = Buffer.from(Uint8Array.from(der)).toString('base64')
    const pem = `-----BEGIN CERTIFICATE-----\n${b64}\n-----END CERTIFICATE-----\n`
    expect(extractLeftmostDnsLabel(pem)).toBe('')
  })
})

describe('getCertSub — njs entrypoint glue over r.variables.ssl_client_cert', () => {
  test('reads ssl_client_cert and returns the leftmost label', async () => {
    const pem = await mintLeafPem([{ type: 'dns', value: 'alice.terminal.yaojia.wang' }])
    expect(getCertSub({ variables: { ssl_client_cert: pem } })).toBe('alice')
  })

  test('missing ssl_client_cert (no client cert) → "" (fail-closed → map denies)', () => {
    expect(getCertSub({ variables: {} })).toBe('')
  })

  test('tab-mangled PEM (nginx $ssl_client_cert continuation form) still parses', async () => {
    const pem = await mintLeafPem([{ type: 'dns', value: 'alice.terminal.yaojia.wang' }])
    // nginx prepends a TAB to every line except the first in $ssl_client_cert.
    const tabbed = pem
      .split('\n')
      .map((line, i) => (i === 0 ? line : `\t${line}`))
      .join('\n')
    expect(getCertSub({ variables: { ssl_client_cert: tabbed } })).toBe('alice')
  })
})

/**
 * JS mirror of the nginx map (zone-anchored, self-sufficient):
 *   map "$cert_sub:$ssl_server_name" $cert_host_ok { "~^(?<s>[^:]+):(?P=s)\\.terminal\\.yaojia\\.wang$" 1; default 0; }
 * PCRE `(?P=s)` is the named backreference; JS spells the same backreference `\k<s>`. Both engines
 * require: a non-empty leftmost label before ':', then the SAME text after ':' followed by EXACTLY
 * `.terminal.yaojia.wang` to the end. Anchoring the full zone (not just `<label>.`) means the map does
 * not rely on the server_name regex / stream SNI routing to constrain the zone. An empty $cert_sub
 * (fail-closed extractor output) can never satisfy `[^:]+` → always denies.
 */
const CERT_HOST_RE = /^(?<s>[^:]+):\k<s>\.terminal\.yaojia\.wang$/
function certHostOk(certSub: string, serverName: string): 0 | 1 {
  return CERT_HOST_RE.test(`${certSub}:${serverName}`) ? 1 : 0
}

describe('certHostOk — map "$cert_sub:$ssl_server_name" allow/deny (mirrors nginx PCRE)', () => {
  test("own host ALLOWS: ('alice','alice.terminal.yaojia.wang') → 1", () => {
    expect(certHostOk('alice', 'alice.terminal.yaojia.wang')).toBe(1)
  })

  test("cross-tenant DENIES: ('alice','bob.terminal.yaojia.wang') → 0  [THE NEGATIVE]", () => {
    expect(certHostOk('alice', 'bob.terminal.yaojia.wang')).toBe(0)
  })

  test("empty cert_sub DENIES: ('','alice.terminal.yaojia.wang') → 0", () => {
    expect(certHostOk('', 'alice.terminal.yaojia.wang')).toBe(0)
  })

  test("prefix attack DENIES: ('alice','alicex.terminal.yaojia.wang') → 0", () => {
    expect(certHostOk('alice', 'alicex.terminal.yaojia.wang')).toBe(0)
  })

  test("no-dot server_name DENIES: ('alice','alice') → 0", () => {
    expect(certHostOk('alice', 'alice')).toBe(0)
  })

  test("empty server_name DENIES: ('alice','') → 0", () => {
    expect(certHostOk('alice', '')).toBe(0)
  })

  test("a second tenant ALLOWS its own host: ('bob','bob.terminal.yaojia.wang') → 1", () => {
    expect(certHostOk('bob', 'bob.terminal.yaojia.wang')).toBe(1)
  })

  test("cross-ZONE bypass DENIES: ('alice','alice.evil.com') → 0  [zone-anchor hardening]", () => {
    // A label-only map (`(?P=s)\.`) would wrongly ALLOW this; the full-zone anchor denies it, so the
    // map stays sound even if the server_name regex / stream SNI routing were ever loosened.
    expect(certHostOk('alice', 'alice.evil.com')).toBe(0)
    expect(certHostOk('alice', 'alice.terminal.yaojia.wang.evil.com')).toBe(0)
  })

  test('end-to-end: extractor output feeds the map (alice cert on bob host → deny)', async () => {
    const pem = await mintLeafPem([{ type: 'dns', value: 'alice.terminal.yaojia.wang' }])
    const sub = extractLeftmostDnsLabel(pem)
    expect(sub).toBe('alice')
    expect(certHostOk(sub, 'alice.terminal.yaojia.wang')).toBe(1)
    expect(certHostOk(sub, 'bob.terminal.yaojia.wang')).toBe(0)
  })
})
