/**
 * Copyright 2023 Google Inc. All rights reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

import {execSync, exec} from 'child_process';
import {writeFile, readFile} from 'fs/promises';
import {promisify} from 'util';

import actions from '@actions/core';
import {PUPPETEER_REVISIONS} from 'puppeteer-core/internal/revisions.js';
import {SemVer} from 'semver';

import packageJson from '../packages/puppeteer-core/package.json' assert {type: 'json'};
import {versionsPerRelease, lastMaintainedChromeVersion} from '../versions.js';

const execAsync = promisify(exec);

const CHROME_CURRENT_VERSION = PUPPETEER_REVISIONS.chrome;
const VERSIONS_PER_RELEASE_COMMENT =
  '// In Chrome roll patches, use `NEXT` for the Puppeteer version.';

const touchedFiles = [];

function checkIfNeedsUpdate(oldVersion, newVersion, newRevision) {
  const oldSemVer = new SemVer(oldVersion, true);
  const newSemVer = new SemVer(newVersion, true);
  let message = `roll to Chrome ${newVersion} (r${newRevision})`;

  if (newSemVer.compare(oldSemVer) <= 0) {
    // Exit the process without setting up version
    console.warn(
      `Version ${newVersion} is older then the current ${oldVersion}`
    );
    process.exit(0);
  } else if (newSemVer.compareMain(oldSemVer) === 0) {
    message = `fix: ${message}`;
  } else {
    message = `feat: ${message}`;
  }
  actions.setOutput('commit', message);
}

/**
 * We cant use `npm run format` as it's too slow
 * so we only scope the files we updated
 */
async function formatUpdateFiles() {
  await Promise.all(
    touchedFiles.map(file => {
      return execAsync(`npx eslint --ext js --ext ts --fix ${file}`);
    })
  );
  await Promise.all(
    touchedFiles.map(file => {
      return execAsync(`npx prettier --write ${file}`);
    })
  );
}

async function replaceInFile(filePath, search, replace) {
  const buffer = await readFile(filePath);
  const update = buffer.toString().replace(search, replace);

  await writeFile(filePath, update);

  touchedFiles.push(filePath);
}

async function getVersionAndRevisionForStable() {
  const result = await fetch(
    'https://googlechromelabs.github.io/chrome-for-testing/last-known-good-versions.json'
  ).then(response => {
    return response.json();
  });

  const {version, revision} = result.channels['Stable'];

  return {
    version,
    revision,
  };
}

async function updateDevToolsProtocolVersion(revision) {
  const currentProtocol = packageJson.dependencies['devtools-protocol'];
  const command = `npm view "devtools-protocol@<=0.0.${revision}" version | tail -1`;

  const bestNewProtocol = execSync(command, {
    encoding: 'utf8',
  })
    .split(' ')[1]
    .replace(/'|\n/g, '');

  await replaceInFile(
    './packages/puppeteer-core/package.json',
    `"devtools-protocol": "${currentProtocol}"`,
    `"devtools-protocol": "${bestNewProtocol}"`
  );
}

async function updateVersionFileLastMaintained(oldVersion, newVersion) {
  const versions = [...versionsPerRelease.keys()];
  if (versions.indexOf(newVersion) !== -1) {
    return;
  }

  // If we have manually rolled Chrome but not yet released
  // We will have NEXT as value in the Map
  if (versionsPerRelease.get(oldVersion) === 'NEXT') {
    await replaceInFile('./versions.js', oldVersion, newVersion);
    return;
  }

  await replaceInFile(
    './versions.js',
    VERSIONS_PER_RELEASE_COMMENT,
    `${VERSIONS_PER_RELEASE_COMMENT}\n  ['${version}', 'NEXT'],`
  );

  const oldSemVer = new SemVer(oldVersion, true);
  const newSemVer = new SemVer(newVersion, true);

  if (newSemVer.compareMain(oldSemVer) !== 0) {
    const lastMaintainedIndex = versions.indexOf(lastMaintainedChromeVersion);
    const nextMaintainedVersion = versions[lastMaintainedIndex - 1];

    await replaceInFile(
      './versions.js',
      `const lastMaintainedChromeVersion = '${lastMaintainedChromeVersion}';`,
      `const lastMaintainedChromeVersion = '${nextMaintainedVersion}';`
    );
  }
}

const {version, revision} = await getVersionAndRevisionForStable();

checkIfNeedsUpdate(CHROME_CURRENT_VERSION, version, revision);

await replaceInFile(
  './packages/puppeteer-core/src/revisions.ts',
  CHROME_CURRENT_VERSION,
  version
);

await updateVersionFileLastMaintained(CHROME_CURRENT_VERSION, version);
await updateDevToolsProtocolVersion(revision);

// Create new `package-lock.json` as we update devtools-protocol
execSync('npm install --ignore-scripts');
// Make sure we pass CI formatter check by running all the new files though it
await formatUpdateFiles();

// Keep this as they can be used to debug GitHub Actions if needed
actions.setOutput('version', version);
actions.setOutput('revision', revision);