/* This file is a part of @mdn/browser-compat-data
 * See LICENSE file for more information. */

import * as fs from 'node:fs';
import { styleText } from 'node:util';

import stringify from '../lib/stringify-and-order-properties.js';

import {
  newBrowserEntry,
  setBrowserReleaseStatus,
  updateBrowserEntry,
} from './utils.js';

/**
 * getFutureReleaseDate - Read the future release date
 * @param {string | number} release The release number of the version
 * @param {string} releaseScheduleURL The url of the MD file having it
 * @returns {Promise<string>} The date in the YYYY-MM-DD format
 * Throws a string in case of an error
 */
const getFutureReleaseDate = async (release, releaseScheduleURL) => {
  // Fetch the MD of the release schedule
  const scheduleHTML = await fetch(releaseScheduleURL);
  const text = await scheduleHTML.text();
  if (!text) {
    throw styleText('red', '\nRelease file not found.');
  }
  /**
   * Find the table row:
   *
   * Example:
   * ```html
   * <tr>
   * <td style="text-align: left;">142</td>                   <-- Version
   * <td style="text-align: left;">Target release</td>
   * <td style="text-align: left;">Week of 9-Oct-2025</td>
   * <td style="text-align: left;">Week of 30-Oct-2025</td>   <-- Stable Channel
   * <td style="text-align: left;">Week of 30-Oct-2025</td>
   * </tr>
   * ```
   */
  const regexp = new RegExp(
    `<td[^>]*>${release}</td>\\s*<td[^>]*>.*</td>\\s*<td[^>]*>.*</td>\\s*<td[^>]*>?Week of (.*)</td>`,
    'i',
  );
  const result = text.match(regexp);
  if (!result) {
    throw styleText(
      'yellow',
      `\nNo entry found for Edge ${release} on [this page](<${releaseScheduleURL}>).`,
    );
  }
  const releaseDateText = result[1];

  const releaseDate = parseReleaseDate(releaseDateText);

  return releaseDate.toISOString().substring(0, 10);
};

/**
 * getReleaseNotesURL - Guess the URL of the release notes
 * @param {string} status The status of the release
 * @param {string | number} version The major version of the release
 * @returns {Promise<string>} The URL of the release notes or the empty string if not found
 * @throws a string in case of error
 */
const getReleaseNotesURL = async (status, version) => {
  const url = `https://learn.microsoft.com/en-us/microsoft-edge/web-platform/release-notes/${version}`;

  const releaseNote = await fetch(url);

  if (releaseNote.status != 200) {
    if (status !== 'Stable') {
      return '';
    }

    throw styleText(
      'red',
      `\nFailed to fetch Edge ${version} release notes at ${url}!`,
    );
  }

  return url;
};

/**
 * updateEdgeReleases - Update the json file listing the browser releases of an Edge browser
 * @param {*} options The list of options for Edge.
 * @returns {Promise<string>} The log of what has been generated (empty if nothing)
 */
export const updateEdgeReleases = async (options) => {
  let result = '';

  //
  // Get the JSON with the versions from edge releases
  //
  const buffer = await fetch(options.edgeupdatesURL);
  const edgeVersions = JSON.parse(await buffer.text());

  //
  // Get the edge.json from the local BCD
  //
  const file = fs.readFileSync(`${options.bcdFile}`);
  const edgeBCD = JSON.parse(file.toString());

  //
  // Update the three channels
  //
  const channels = new Map([
    ['current', options.releaseBranch],
    ['beta', options.betaBranch],
    ['nightly', options.nightlyBranch],
  ]);

  // Information store to be used outside the for loop
  const data = {};

  for (const [key, value] of channels) {
    //
    // Extract the useful data
    //

    // Find the right entry
    const entry = edgeVersions.find((e) => e['Product'] === value);

    // Get the last version
    const version = parseFloat(entry['Releases'][0]['ProductVersion']);

    // We need to find the first release with this version.
    // As, they are from the newer to the older in the JSON object,
    // we are looking for the first that is not the latest version
    // and get the one immediately above (the first release of that
    // version)
    //
    // The full version number is needed for the release note URL
    // as well as the date
    let id = 1;
    while (
      id < entry['Releases'].length &&
      parseFloat(entry['Releases'][id]['ProductVersion']) === version
    ) {
      id++;
    }
    id--;

    data[value] = {};
    data[value].version = parseFloat(entry['Releases'][id]['ProductVersion']);
    data[value].fullVersion = entry['Releases'][id]['ProductVersion'];

    //
    // Update the JSON in memory
    //

    // We skip beta and nightly versions if they are of the same version as the released one
    if (
      key === 'current' ||
      (key !== 'current' &&
        data[value].version !== data[channels.get('current')].version)
    ) {
      // NOTE: the published date is the one of the actual release
      // (being the Stable, Beta, or Nightly),
      // and not the one of the future release like we would like
      // So we only get it if we are on the 'current' channel.

      // Get published date
      if (key === 'current') {
        data[value].versionDate = entry['Releases'][id][
          'PublishedTime'
        ].substring(0, 10); // Remove the time part;
      } else {
        try {
          data[value].versionDate = await getFutureReleaseDate(
            data[value].version,
            options.releaseScheduleURL,
          );
        } catch (str) {
          result += str;
        }
      }

      // Get the release notes
      let releaseNotesURL = '';
      try {
        releaseNotesURL = await getReleaseNotesURL(value, data[value].version);
      } catch (s) {
        result += s;
      }

      if (
        edgeBCD.browsers[options.bcdBrowserName].releases[data[value].version]
      ) {
        // The entry already exists
        result += updateBrowserEntry(
          edgeBCD,
          options.bcdBrowserName,
          data[value].version,
          data[value]?.versionDate,
          key,
          releaseNotesURL,
          '',
        );
      } else {
        // New entry
        result += newBrowserEntry(
          edgeBCD,
          options.bcdBrowserName,
          data[value].version,
          key,
          options.browserEngine,
          data[value]?.versionDate,
          releaseNotesURL,
          data[value].version,
        );
      }
    }
  }

  //
  // Check that all older releases are 'retired'
  //
  for (
    let i = options.firstRelease;
    i < data[options.releaseBranch].version;
    i++
  ) {
    if (!options.skippedReleases.includes(i)) {
      if (edgeBCD.browsers[options.bcdBrowserName].releases[i.toString()]) {
        result += updateBrowserEntry(
          edgeBCD,
          options.bcdBrowserName,
          i.toString(),
          edgeBCD.browsers[options.bcdBrowserName].releases[i.toString()]
            .release_date,
          'retired',
          '',
          '',
        );
      } else {
        // There is a retired version missing. Edgeupdates doesn't list them.
        // There is an oddity: the version is not skipped but not in edgeupdates
        result += styleText(
          'yellow',
          `\nEdge ${i} not found in Edgeupdates! Add it manually or add an exception.`,
        );
      }
    }
  }

  //
  // Ensure that the release following stable is 'beta'
  //
  const betaVersion = data[options.releaseBranch].version + 1;
  result += setBrowserReleaseStatus(
    edgeBCD,
    options.bcdBrowserName,
    betaVersion.toString(),
    'beta',
  );

  //
  // Ensure that the release following beta is 'nightly'
  //
  const nightlyVersion = data[options.releaseBranch].version + 2;
  result += setBrowserReleaseStatus(
    edgeBCD,
    options.bcdBrowserName,
    nightlyVersion.toString(),
    'nightly',
  );

  //
  // Add a planned version entry
  //
  const planned = (data[options.releaseBranch].version + 3).toString();
  let releaseDate;
  try {
    releaseDate = await getFutureReleaseDate(
      planned,
      options.releaseScheduleURL,
    );
  } catch (s) {
    result += s;
  }

  if (edgeBCD.browsers[options.bcdBrowserName].releases[planned]) {
    result += updateBrowserEntry(
      edgeBCD,
      options.bcdBrowserName,
      planned,
      releaseDate,
      'planned',
      '',
      planned,
    );
  } else {
    // New entry
    result += newBrowserEntry(
      edgeBCD,
      options.bcdBrowserName,
      planned.toString(),
      'planned',
      options.browserEngine,
      releaseDate,
      '',
      planned.toString(),
    );
  }

  //
  // Write the update browser's json to file
  //
  fs.writeFileSync(`./${options.bcdFile}`, stringify(edgeBCD) + '\n');

  // Returns the log
  if (result) {
    result = `### Updates for ${options.browserName}\n${result}`;
  }
  return result;
};

/**
 * Parses a release date from the Edge release table.
 * @param {string} dateText - Release date in the original format.
 * @returns string
 */
export const parseReleaseDate = (dateText) => {
  const months = [
    'Jan',
    'Feb',
    'Mar',
    'Apr',
    'May',
    'Jun',
    'Jul',
    'Aug',
    'Sep',
    'Oct',
    'Nov',
    'Dec',
  ];
  const [day, monthText, year] = dateText.split('-');
  const month = months.findIndex((m) => monthText.startsWith(m)) + 1;

  return new Date(`${year}-${month}-${day}Z`);
};
