import { createHash } from 'node:crypto'
import { lstat, readFile, readdir, writeFile } from 'node:fs/promises'
import { resolve } from 'node:path'
import { signManifest, verifyManifestSignature } from './release-signature.mjs'

const root = resolve(new URL('..', import.meta.url).pathname)
const distRoot = resolve(root, 'dist')
const sbomPath = resolve(root, 'config/sbom.cdx.json')
const outputPath = resolve(root, 'config/release-artifacts.json')
const publicKeyPath = resolve(root, 'config/release-signing-public.pem')
const checkOnly = process.argv.includes('--check')

const hashFile = async (path) => {
  const hash = createHash('sha256')
  hash.update(await readFile(path))
  return hash.digest('hex')
}
const collectFiles = async (directory, prefix = '') => {
  const entries = []
  for (const entry of (await readdir(directory, { withFileTypes: true })).sort((left, right) => left.name.localeCompare(right.name))) {
    const relative = prefix ? `${prefix}/${entry.name}` : entry.name
    const path = resolve(directory, entry.name)
    const stats = await lstat(path)
    if (stats.isSymbolicLink()) throw new Error(`Release artifact contains a symlink: dist/${relative}`)
    if (entry.isDirectory()) entries.push(...await collectFiles(path, relative))
    else if (entry.isFile()) entries.push({ path: relative, bytes: stats.size, sha256: await hashFile(path) })
    else throw new Error(`Release artifact is not a regular file: dist/${relative}`)
  }
  return entries
}

const files = await collectFiles(distRoot)
if (!files.length) throw new Error('Release artifact directory dist is empty; run `npm run build` first.')
const sbomSha256 = await hashFile(sbomPath)
const packageManifest = JSON.parse(await readFile(resolve(root, 'package.json'), 'utf8'))
const unsigned = {
  schemaVersion: 1,
  package: { name: packageManifest.name, version: packageManifest.version },
  build: { output: 'dist', files },
  sbom: { path: 'config/sbom.cdx.json', sha256: sbomSha256 },
}
const signingKey = process.env.RELEASE_SIGNING_PRIVATE_KEY
const signingKeyId = process.env.RELEASE_SIGNING_KEY_ID
const signable = JSON.stringify(unsigned)
let document
if (checkOnly) {
  try { document = JSON.parse(await readFile(outputPath, 'utf8')) } catch { throw new Error(`Release artifact manifest is missing: ${outputPath}`) }
  const actualUnsigned = { schemaVersion: document.schemaVersion, package: document.package, build: document.build, sbom: document.sbom }
  if (JSON.stringify(actualUnsigned) !== signable) throw new Error('Release artifact manifest is stale; regenerate with the controlled signing key.')
  if (document.signature?.status !== 'signed' || document.signature.algorithm !== 'Ed25519') throw new Error('Release artifact manifest is unsigned; REL-03 requires a signed artifact.')
  let publicKey
  try { publicKey = await readFile(publicKeyPath, 'utf8') } catch { throw new Error(`Release signing public key is missing: ${publicKeyPath}`) }
  if (!verifyManifestSignature(signable, document.signature, publicKey)) throw new Error('Release artifact signature verification failed.')
  console.log(JSON.stringify({ status: 'release-artifacts-pass', files: files.length, totalBytes: files.reduce((sum, file) => sum + file.bytes, 0), sbomSha256, signature: document.signature.status, keyId: document.signature.keyId }, null, 2))
} else {
  if (signingKey || signingKeyId) document = { ...unsigned, signature: signManifest(signable, signingKey, signingKeyId) }
  else {
    try {
      const existing = JSON.parse(await readFile(outputPath, 'utf8'))
      const existingUnsigned = { schemaVersion: existing.schemaVersion, package: existing.package, build: existing.build, sbom: existing.sbom }
      const publicKey = await readFile(publicKeyPath, 'utf8')
      if (JSON.stringify(existingUnsigned) !== signable || !verifyManifestSignature(signable, existing.signature, publicKey)) throw new Error('existing signed manifest does not match current build')
      document = existing
    } catch (error) {
      throw new Error(`Release generation requires RELEASE_SIGNING_PRIVATE_KEY/RELEASE_SIGNING_KEY_ID for a new or changed build: ${error instanceof Error ? error.message : String(error)}`)
    }
  }
  const serialized = `${JSON.stringify(document, null, 2)}\n`
  await writeFile(outputPath, serialized)
  console.log(JSON.stringify({ status: 'release-artifacts-generated', output: outputPath, files: files.length, totalBytes: files.reduce((sum, file) => sum + file.bytes, 0), sbomSha256, signature: document.signature.status }, null, 2))
}
