import { describe, expect, it } from 'vitest'
import { X509Certificate, createPublicKey, verify } from 'node:crypto'
import { generateIdentity, generateP256Identity } from '../src/keys/identity.js'
import { buildCsr } from '../src/enroll/csr.js'

// --- minimal DER reader (test-only) — walks the PKCS#10 outer SEQUENCE into its 3 children -------
interface Tlv {
  readonly tag: number
  /** the full tag+length+value bytes (what was signed, for the CertificationRequestInfo). */
  readonly tlv: Uint8Array
  readonly content: Uint8Array
}

function readTlv(buf: Uint8Array, off: number): { node: Tlv; next: number } {
  const tag = buf[off]!
  let i = off + 1
  const first = buf[i]!
  let len: number
  if (first < 0x80) {
    len = first
    i += 1
  } else {
    const n = first & 0x7f
    len = 0
    for (let k = 0; k < n; k++) len = (len << 8) | buf[i + 1 + k]!
    i += 1 + n
  }
  return { node: { tag, tlv: buf.subarray(off, i + len), content: buf.subarray(i, i + len) }, next: i + len }
}

function pemToDer(pem: string): Uint8Array {
  const b64 = pem.replace(/-----[A-Z ]+-----/g, '').replace(/\s+/g, '')
  return new Uint8Array(Buffer.from(b64, 'base64'))
}

/** The three children of the PKCS#10 outer SEQUENCE: [certificationRequestInfo, sigAlg, signature]. */
function csrChildren(pem: string): readonly Tlv[] {
  const der = pemToDer(pem)
  const outer = readTlv(der, 0).node
  const children: Tlv[] = []
  let p = 0
  while (p < outer.content.length) {
    const { node, next } = readTlv(outer.content, p)
    children.push(node)
    p = next
  }
  return children
}

describe('PKCS#10 CSR (T4)', () => {
  it('emits a PEM CERTIFICATE REQUEST', () => {
    const csr = buildCsr(generateIdentity(), 'host-42.term.example.com')
    expect(csr).toContain('-----BEGIN CERTIFICATE REQUEST-----')
    expect(csr).toContain('-----END CERTIFICATE REQUEST-----')
  })

  it('does not contain private-key material (INV4)', () => {
    const id = generateIdentity()
    const csr = buildCsr(id, 'host-42')
    expect(csr).not.toContain('PRIVATE KEY')
    expect(csr).not.toContain(id.exportPrivatePkcs8Pem().split('\n')[1]!)
  })

  it('produces a syntactically decodable DER structure', () => {
    // Node can't parse a CSR directly, but the base64 body must be valid DER (a SEQUENCE).
    const csr = buildCsr(generateIdentity(), 'host-42')
    const b64 = csr
      .replace(/-----[A-Z ]+-----/g, '')
      .replace(/\s+/g, '')
    const der = Buffer.from(b64, 'base64')
    expect(der[0]).toBe(0x30) // outer SEQUENCE tag
    expect(der.length).toBeGreaterThan(64)
    // sanity: X509Certificate exists (crypto available) — unrelated smoke to keep import used
    expect(typeof X509Certificate).toBe('function')
  })
})

describe('P-256 PKCS#10 CSR (FIX H-host-2)', () => {
  it('emits a PEM CERTIFICATE REQUEST for a P-256 identity', () => {
    const csr = buildCsr(generateP256Identity(), 'alice.terminal.yaojia.wang')
    expect(csr).toContain('-----BEGIN CERTIFICATE REQUEST-----')
    expect(csr).toContain('-----END CERTIFICATE REQUEST-----')
    expect(csr).not.toContain('PRIVATE KEY')
  })

  it('round-trips as a valid PKCS#10: signatureAlgorithm is ecdsa-with-SHA256', () => {
    const csr = buildCsr(generateP256Identity(), 'alice.terminal.yaojia.wang')
    const [, sigAlg] = csrChildren(csr)
    // sigAlg = SEQUENCE { OID 1.2.840.10045.4.3.2 } (no parameters, RFC 5758 §3.2)
    const oidTlv = readTlv(sigAlg!.content, 0).node
    expect(Array.from(oidTlv.content)).toEqual([0x2a, 0x86, 0x48, 0xce, 0x3d, 0x04, 0x03, 0x02])
    // no parameters: the sigAlg SEQUENCE holds ONLY the OID.
    expect(oidTlv.tlv.length).toBe(sigAlg!.content.length)
  })

  it('self-signature verifies over the CertificationRequestInfo (verifyCsrPoPEc semantics)', () => {
    const id = generateP256Identity()
    const csr = buildCsr(id, 'alice.terminal.yaojia.wang')
    const [reqInfo, , sigVal] = csrChildren(csr)
    // signatureValue BIT STRING content = 0x00 (unused bits) || DER ECDSA-Sig-Value.
    expect(sigVal!.tag).toBe(0x03)
    const signature = sigVal!.content.subarray(1)
    const pub = createPublicKey({ key: Buffer.from(id.publicKey), format: 'der', type: 'spki' })
    // Verify over the EXACT CertificationRequestInfo bytes that buildCsr signed.
    expect(verify('sha256', reqInfo!.tlv, pub, signature)).toBe(true)
    // Tampering with the signed body breaks PoP.
    const tampered = Uint8Array.from(reqInfo!.tlv)
    tampered[tampered.length - 1] = tampered[tampered.length - 1]! ^ 0xff
    expect(verify('sha256', tampered, pub, signature)).toBe(false)
  })

  it('embeds the identity EC SPKI verbatim as the CSR subjectPublicKeyInfo', () => {
    const id = generateP256Identity()
    const csr = buildCsr(id, 'alice.terminal.yaojia.wang')
    const [reqInfo] = csrChildren(csr)
    // requestInfo = SEQUENCE { version, name, spki, [0] attributes }; the spki is the 3rd child.
    const inner = reqInfo!.content
    const version = readTlv(inner, 0)
    const name = readTlv(inner, version.next)
    const spki = readTlv(inner, name.next).node
    expect(Buffer.from(spki.tlv).equals(Buffer.from(id.publicKey))).toBe(true)
  })
})
