/**
 * @license
 * Copyright Google LLC All Rights Reserved.
 *
 * Use of this source code is governed by an MIT-style license that can be
 * found in the LICENSE file at https://angular.dev/license
 */

//tslint:disable:no-console
import assert from 'node:assert';
import {execSync} from 'node:child_process';
import {existsSync, constants as fsConstants} from 'node:fs';
import {
  copyFile,
  mkdtemp,
  readdir,
  readFile,
  realpath,
  rm,
  unlink,
  writeFile,
} from 'node:fs/promises';
import {tmpdir} from 'node:os';
import {join} from 'node:path';
import {GithubClient} from './github-client.mjs';

export async function updateAssets({repo, assetsPath, destPath}) {
  console.log('\n-----------------------------------------------');
  console.log(`Processing: ${repo}`);
  console.log('-----------------------------------------------\n');

  const buildInfoPath = join(destPath, '_build-info.json');
  if (!existsSync(buildInfoPath)) {
    throw new Error(`${buildInfoPath} does not exist.`);
  }

  assert(process.env.GITHUB_REF);
  const currentBranch = process.env.GITHUB_REF;

  const {sha: storedSha, branchName: storedBranch} = JSON.parse(
    await readFile(buildInfoPath, 'utf-8'),
  );

  assert(process.env.ANGULAR_READONLY_GITHUB_TOKEN);
  const githubApi = new GithubClient(
    repo,
    process.env.ANGULAR_READONLY_GITHUB_TOKEN,
    'ADEV_Cross_Repo_Docs_Update',
  );

  let downstreamBranch = currentBranch;
  let latestSha = await githubApi.getShaForBranch(currentBranch);
  if (
    latestSha.includes('No commit') &&
    currentBranch !== 'refs/heads/main' &&
    currentBranch !== storedBranch
  ) {
    // In some cases, such as when a new branch is created for a feature,
    // the branch may not exist in the downstream repo. For example, during an
    // exceptional minor release (e.g. FW 20.3.x and Components: 20.2.x).
    // In such scenarios, we fallback to the last known branch.
    latestSha = await githubApi.getShaForBranch(storedBranch);
    downstreamBranch = storedBranch;
  }

  console.log(`Comparing ${storedSha}...${latestSha}.`);
  const affectedFiles = await githubApi.getAffectedFiles(storedSha, latestSha);
  const changedFiles = affectedFiles.filter((file) => file.startsWith(`${assetsPath}/`));

  let shaWhenFilesChanged;
  if (changedFiles.length > 0) {
    console.log(
      `The below files changed between ${storedSha} and ${latestSha}:\n` +
        changedFiles.map((f) => '* ' + f).join('\n'),
    );

    const temporaryDir = await realpath(await mkdtemp(join(tmpdir(), 'update-assets-')));

    try {
      const execOptions = {cwd: temporaryDir, stdio: 'inherit'};
      execSync('git init', execOptions);
      execSync(`git remote add origin https://github.com/${repo}.git`, execOptions);
      // fetch a commit
      execSync(`git fetch origin ${latestSha}`, execOptions);
      // reset this repository's main branch to the commit of interest
      execSync('git reset --hard FETCH_HEAD', execOptions);
      // get sha when files where changed
      shaWhenFilesChanged = execSync(`git rev-list -1 ${latestSha} "${assetsPath}/"`, {
        encoding: 'utf8',
        cwd: temporaryDir,
        stdio: ['ignore', 'pipe', 'ignore'],
      }).trim();

      // Delete existing asset files.
      const apiFilesUnlink = (await readdir(destPath))
        .filter((f) => f.endsWith('.json'))
        .map((f) => unlink(join(destPath, f)));

      await Promise.allSettled(apiFilesUnlink);

      // Copy new asset files
      const tempAssetsDir = join(temporaryDir, assetsPath);
      const assetFilesCopy = (await readdir(tempAssetsDir)).map((f) => {
        const src = join(tempAssetsDir, f);
        const dest = join(destPath, f);

        return copyFile(src, dest, fsConstants.COPYFILE_FICLONE);
      });

      await Promise.allSettled(assetFilesCopy);
    } finally {
      await rm(temporaryDir, {force: true, recursive: true});
    }

    console.log(`Successfully updated asset files in '${destPath}'.\n`);
  } else {
    console.log(`No '${assetsPath}/**' files changed between ${storedSha} and ${latestSha}.`);
  }

  // Write SHA to file.
  await writeFile(
    buildInfoPath,
    JSON.stringify(
      {
        branchName: downstreamBranch,
        sha: shaWhenFilesChanged ?? storedSha,
      },
      undefined,
      2,
    ),
  );
}
