const { ScreepsAPI } = require("screeps-api");
const core = require("@actions/core");
const fs = require("fs");
const { glob } = require("glob");
const path = require("path");

/**
 * Replaces specific placeholder strings within the provided content with corresponding dynamic values.
 *
 * This function specifically targets three placeholders:
 * - {{gitHash}} is replaced with the current Git commit hash, obtained from the GITHUB_SHA environment variable.
 * - {{gitRef}} is replaced with the Git reference (branch or tag) that triggered the workflow, obtained from the GITHUB_REF environment variable.
 * - {{deployTime}} is replaced with the current ISO timestamp.
 *
 * Note: This function is designed for use within a GitHub Actions workflow where GITHUB_SHA and GITHUB_REF environment variables are automatically set.
 *
 * @param {string} content - The string content in which placeholders are to be replaced.
 * @returns {string} The content with placeholders replaced by their respective dynamic values.
 */
function replacePlaceholders(content, hostname) {
  const deployTime = new Date().toISOString();
  return content
    .replace(/{{gitHash}}/g, process.env.GITHUB_SHA)
    .replace(/{{gitRef}}/g, process.env.GITHUB_REF)
    .replace(/{{deployTime}}/g, deployTime)
    .replace(/{{hostname}}/g, hostname);
}

/**
 * Reads all files matching a specified pattern, replaces certain placeholders in their content, and writes the updated content back to the files.
 *
 * This function searches for files in the filesystem using the provided glob pattern, optionally prefixed. It reads each file,
 * uses the `replacePlaceholders` function to replace specific placeholders in the file's content, and then writes the modified content
 * back to the original file. This is useful for dynamically updating file contents in a batch process, such as during a build or deployment.
 *
 * @param {string} pattern - The glob pattern used to find files. Example: '*.js' for all JavaScript files.
 * @param {string} [prefix] - An optional directory prefix to prepend to the glob pattern. This allows searching within a specific directory.
 * @returns {Promise<string[]>} A promise that resolves with an array of file paths that were processed, or rejects with an error if the process fails.
 */
async function readReplaceAndWriteFiles(pattern, prefix, hostname) {
  return new Promise((resolve, reject) => {
    const globPattern = prefix ? path.join(prefix, pattern) : pattern;

    glob(globPattern, async (err, files) => {
      if (err) {
        return reject(err);
      }
      let processPromises = [];

      files.forEach((file) => {
        let processPromise = fs.promises
          .readFile(file, "utf8")
          .then((content) => {
            content = replacePlaceholders(content, hostname);
            return fs.promises.writeFile(file, content);
          });

        processPromises.push(processPromise);
      });

      try {
        await Promise.all(processPromises);
        resolve(files);
      } catch (processError) {
        reject(processError);
      }
    });
  });
}

/**
 * Reads files matching a glob pattern into a dictionary.
 * @param {string} pattern - Glob pattern to match files.
 * @param {string} prefix - Directory prefix for file paths.
 * @returns {Promise<Object>} - Promise resolving to a dictionary of file contents keyed by filenames.
 */
function readFilesIntoDict(pattern, prefix) {
  // Prepend the prefix to the glob pattern
  const globPattern = prefix ? path.join(prefix, pattern) : pattern;

  return new Promise((resolve, reject) => {
    glob(globPattern, (err, files) => {
      if (err) {
        return reject(err);
      }

      let fileDict = {};
      let readPromises = [];

      files.forEach((file) => {
        let readPromise = fs.promises.readFile(file, "utf8").then((content) => {
          // Remove the prefix from the filename and drop the file suffix
          let key = file;
          if (prefix && file.startsWith(prefix)) {
            key = key.slice(prefix.length);
          }
          key = path.basename(key, path.extname(key)); // Drop the file suffix

          fileDict[key] = content;
        });

        readPromises.push(readPromise);
      });

      // Use Promise.all to ensure all files are read before resolving
      Promise.all(readPromises)
        .then(() => resolve(fileDict))
        .catch(reject);
    });
  });
}

/**
 * Validates the provided authentication credentials.
 * @param {string} token - The authentication token.
 * @param {string} username - The username.
 * @param {string} password - The password.
 * @returns {string|null} - Returns an error message if validation fails, otherwise null.
 */
function validateAuthentication(token, username, password) {
  if (token) {
    if (username || password) {
      return "Token is defined along with username and/or password.";
    }
  } else {
    if (!username && !password) {
      return "Neither token nor password and username are defined.";
    }
    if (username && !password) {
      return "Username is defined but no password is provided.";
    }
    if (!username && password) {
      return "Password is defined but no username is provided.";
    }
  }
  return null; // No errors found
}

/**
 * Posts code to Screeps server.
 */
async function postCode() {
  const protocol = core.getInput("protocol") || "https";
  const hostname = core.getInput("hostname") || "screeps.com";
  const port = core.getInput("port") || "443";
  const path = core.getInput("path") || "/";

  const token = core.getInput("token") || undefined;
  const username = core.getInput("username") || undefined;
  const password = core.getInput("password") || undefined;
  const prefix = core.getInput("source-prefix");
  const pattern = core.getInput("pattern") || "*.js";
  const branch = core.getInput("branch") || "default";

  const gitReplace = core.getInput("git-replace") || null;

  if (gitReplace) {
    await readReplaceAndWriteFiles(gitReplace, prefix, hostname);
  }

  const files_to_push = await readFilesIntoDict(pattern, prefix);

  core.info(`Trying to upload the following files to ${branch}:`);
  Object.keys(files_to_push).forEach((key) => {
    core.info(`Key: ${key}`);
  });

  const login_arguments = {
    token: token,
    username: username,
    password: password,
    protocol: protocol,
    hostname: hostname,
    port: port,
    path: path,
  };

  core.info("login_arguments:");
  core.info(JSON.stringify(login_arguments, null, 2));

  const errorMessage = validateAuthentication(token, username, password);
  if (errorMessage) {
    core.error(errorMessage);
    return;
  }
  let api = new ScreepsAPI(login_arguments);
  if (token) {
    const response = await api.code.set(branch, files_to_push);
    core.info(JSON.stringify(response, null, 2));
    console.log(`Code set successfully to ${branch}`);
  } else {
    core.info(`Logging in as user ${username}`);
    await Promise.resolve()
      .then(() => api.auth(username, password, login_arguments))
      .then(() => {
        api.code.set(branch, files_to_push);
      })
      .then(() => {
        console.log(`Code set successfully to ${branch}`);
      })
      .catch((err) => {
        console.error("Error:", err);
      });
  }
}
postCode();
