const path = require("path");
const { attestProvenance } = require("@actions/attest");
const { execSync } = require("child_process");
const ghRelease = require("gh-release");
const fs = require("fs");

// This script is used to publish the artifacts generated by the changeset workflow.
// It is triggered by the changeset action and is responsible for creating releases to the repository.
// We do this manually because we want specific/scoped assets to be uploaded to a corresponding SCOPED releases.
// e.g. nsis artifacts should be uploaded to the nsis release, not in a zstd release.
// JSON format: https://github.com/changesets/action?tab=readme-ov-file#outputs
const publishedPackages = process.env.PUBLISHED_PACKAGES;
if (!publishedPackages) {
  console.error("PUBLISHED_PACKAGES environment variable is not set. See script for documentation.");
  process.exit(1);
}

async function releaseArtifact(options, token, releaseName) {
  await new Promise((resolve, reject) => ghRelease({ ...options, auth: { token } }, (err) => {
    if (err) {
      return reject(err);
    }
    console.log(`Artifacts for ${releaseName} uploaded successfully.`);
    resolve();
  })
  );
}

async function run() {
  const releases = JSON.parse(publishedPackages);

  console.log("Release candidates:", releases);

  const releaseOptions = {
    draft: false,
    prerelease: false,
    yes: true,
  };

  const isCi = !!process.env.CI;
  if (!isCi) {
    console.log("CI not detected, blocking remote release. Only logging release config to console...");
  }

  const artifactPath = (artifact) => path.resolve(__dirname, "../artifacts", artifact);

  for (const release of releases) {
    const { name, version } = release;
    const artifactsToUpload = fs.readdirSync(artifactPath(name)).filter((file) => {
      return !file.endsWith(".checksum.txt") && !file.startsWith(".");
    });
    if (!artifactsToUpload) {
      throw new Error(`No artifacts found for ${name}`);
    }
    const releaseName = `${name}@${version}`;

    const checksums = artifactsToUpload.map((artifactName) => {
      const resolvedArtifactPath = artifactPath(path.join(name, artifactName));
      // Generate checksums
      const checksum512 = execSync(`shasum -a 512 "${resolvedArtifactPath}" | xxd -r -p | base64`).toString().trim();
      const digest256 = execSync(`shasum -a 256 "${resolvedArtifactPath}" | cut -d ' ' -f 1`).toString().trim();
      if (!checksum512 || !digest256) {
        throw new Error(`Failed to generate checksums for ${resolvedArtifactPath}`);
      }
      return { name: artifactName, checksum512, digest256 };
    });

    const bodyText = checksums
      .map(({ name, checksum512 }) => `\`${name}\`: \`${checksum512}\``)
      .sort()
      .join("\n");
    const options = {
      ...releaseOptions,
      name: releaseName,
      tag_name: releaseName,
      body: `*checksums*\n\n${bodyText}`,
      assets: artifactsToUpload.map((artifact) => path.join("artifacts", name, artifact)),
    };

    if (!isCi) {
      console.log("\n\nRelease options:", options);
      continue;
    }
    // If CI, validate token is present
    const token = process.env.GITHUB_TOKEN;
    if (!token) {
      console.error("GITHUB_TOKEN environment variable is not set.");
      process.exit(1);
    }

    console.log(`Attesting artifacts for ${releaseName}...`);
    const subjects = checksums.map(({ name, digest256 }) => ({ name, digest: { sha256: digest256 } }));
    await attestProvenance({ token, subjects });
    console.log("Attestation successful for artifacts:", subjects);

    console.log(`Uploading artifacts for ${releaseName}...`);
    await releaseArtifact(options, token, releaseName).catch(async (err) => {
      console.error(`Failed to upload artifacts for ${releaseName}:`, err);
      if (err.status === 500) {
        console.error("500 error: Retrying release in 5 seconds.");
        await new Promise((resolve) => setTimeout(resolve, 5000));
        return releaseArtifact(options, token, releaseName);
      }
      throw err
    })
    console.log(`Artifacts for ${releaseName} uploaded successfully.`);
  }
}

run();