import { createHash, createPublicKey, verify } from 'node:crypto';
import { readFileSync } from 'node:fs';
import { pathToFileURL } from 'node:url';

const signatureKeys = [
  'algorithm', 'key_id', 'manifest_sha256', 'manifest_url', 'schema', 'signature',
];
const releaseKeys = [
  'artifact', 'channel', 'documentation', 'packages', 'platforms', 'published_on',
  'requirements', 'schema', 'stable', 'validation', 'version',
];

export function verifySdkRelease({ signatureDocument, manifestBytes, archiveBytes, publicKeyPem }) {
  const signature = parseObject(signatureDocument, 'signature document');
  if (JSON.stringify(Object.keys(signature).sort()) !== JSON.stringify(signatureKeys)) {
    throw new Error('SDK release signature has an unexpected shape.');
  }
  if (signature.schema !== 'xaere-sdk-release-signature-v1' || signature.algorithm !== 'Ed25519') {
    throw new Error('SDK release signature algorithm is unsupported.');
  }
  if (!/^sha256:[a-f0-9]{64}$/.test(signature.key_id ?? '')) {
    throw new Error('SDK release key identifier is invalid.');
  }
  if (!/^[a-f0-9]{64}$/.test(signature.manifest_sha256 ?? '')) {
    throw new Error('SDK release manifest digest is invalid.');
  }
  if (!/^[A-Za-z0-9_-]{86}$/.test(signature.signature ?? '')) {
    throw new Error('SDK release signature encoding is invalid.');
  }
  const manifestUrl = new URL(signature.manifest_url);
  const previewMatch = /^\/releases\/([0-9]+\.[0-9]+\.[0-9]+)-(r[1-9][0-9]*)\.json$/.exec(manifestUrl.pathname);
  const stableMatch = /^\/releases\/([0-9]+\.[0-9]+\.[0-9]+)-stable-(r[1-9][0-9]*)\.json$/.exec(manifestUrl.pathname);
  const manifestMatch = previewMatch ?? stableMatch;
  if (manifestUrl.protocol !== 'https:' || manifestUrl.username || manifestUrl.password
      || manifestUrl.hostname !== 'sdk.xaere.io'
      || !manifestMatch
      || manifestUrl.search || manifestUrl.hash) {
    throw new Error('SDK release manifest URL is invalid.');
  }

  const publicKey = createPublicKey(publicKeyPem);
  if (publicKey.asymmetricKeyType !== 'ed25519') {
    throw new Error('SDK release public key is not Ed25519.');
  }
  const keyId = `sha256:${createHash('sha256').update(publicKey.export({ type: 'spki', format: 'der' })).digest('hex')}`;
  if (keyId !== signature.key_id) throw new Error('SDK release public key identifier mismatch.');

  const manifest = Buffer.from(manifestBytes);
  if (createHash('sha256').update(manifest).digest('hex') !== signature.manifest_sha256) {
    throw new Error('SDK release manifest digest mismatch.');
  }
  if (!verify(null, manifest, publicKey, Buffer.from(signature.signature, 'base64url'))) {
    throw new Error('SDK release signature verification failed.');
  }

  const release = parseObject(manifest.toString('utf8'), 'release manifest');
  const expectedChannel = stableMatch ? 'stable' : 'preview';
  const expectedStable = Boolean(stableMatch);
  const artifactSuffix = stableMatch ? `stable-${stableMatch[2]}` : `preview-${previewMatch[2]}`;
  if (JSON.stringify(Object.keys(release).sort()) !== JSON.stringify(releaseKeys)
      || release.schema !== 'xaere-flutter-sdk-release-v1' || release.channel !== expectedChannel
      || release.version !== manifestMatch[1] || release.stable !== expectedStable) {
    throw new Error('SDK release manifest is unsupported.');
  }
  const artifact = release.artifact;
  if (!artifact || JSON.stringify(Object.keys(artifact).sort()) !== JSON.stringify(['bytes', 'sha256', 'url'])
      || !Number.isSafeInteger(artifact.bytes) || artifact.bytes < 1
      || !/^[a-f0-9]{64}$/.test(artifact.sha256 ?? '')
      || artifact.url !== `https://sdk.xaere.io/downloads/xaere-flutter-sdk-${release.version}-${artifactSuffix}.tar.gz`) {
    throw new Error('SDK release artifact contract is invalid.');
  }
  const archive = Buffer.from(archiveBytes);
  if (archive.length !== artifact.bytes) throw new Error('SDK release archive length mismatch.');
  if (createHash('sha256').update(archive).digest('hex') !== artifact.sha256) {
    throw new Error('SDK release archive digest mismatch.');
  }
  return Object.freeze({
    version: release.version,
    artifact_sha256: artifact.sha256,
    key_id: keyId,
    status: 'verified',
  });
}

function parseObject(value, label) {
  let parsed;
  try {
    parsed = typeof value === 'string' ? JSON.parse(value) : value;
  } catch {
    throw new Error(`${label} is not valid JSON.`);
  }
  if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) {
    throw new Error(`${label} must be a JSON object.`);
  }
  return parsed;
}

if (process.argv[1] && pathToFileURL(process.argv[1]).href === import.meta.url) {
  if (process.argv.length !== 6) {
    console.error('Usage: node verify-sdk-release.mjs SIGNATURE.json RELEASE.json SDK.tar.gz PUBLIC.pem');
    process.exit(64);
  }
  try {
    const result = verifySdkRelease({
      signatureDocument: readFileSync(process.argv[2], 'utf8'),
      manifestBytes: readFileSync(process.argv[3]),
      archiveBytes: readFileSync(process.argv[4]),
      publicKeyPem: readFileSync(process.argv[5]),
    });
    console.log(JSON.stringify(result));
  } catch (error) {
    console.error(`SDK release verification failed: ${error instanceof Error ? error.message : 'unknown error'}`);
    process.exit(1);
  }
}
