const fs = require("node:fs");
const path = require("node:path");
const { stripVTControlCharacters } = require("node:util");

const ROOT_PATH = process.env.GITHUB_WORKSPACE || path.resolve(__dirname, "..");

const getPkgPath = (pkg, isSubPackage) => {
  const pkgPath = isSubPackage ? `./node_modules/@webpack-cli/${pkg}` : `./node_modules/${pkg}`;

  return path.resolve(ROOT_PATH, pkgPath);
};

const swapPkgName = (current, isSubPackage = false) => {
  // info -> .info and vice-versa
  const next = current.startsWith(".") ? current.slice(1) : `.${current}`;

  console.log(`  swapping ${current} with ${next}`);

  fs.renameSync(getPkgPath(current, isSubPackage), getPkgPath(next, isSubPackage));
};

const CLI_ENTRY_PATH = path.resolve(ROOT_PATH, "./packages/webpack-cli/bin/cli.js");

const runTest = async (pkg, cliArgs = [], logMessage = undefined, isSubPackage = false) => {
  // Simulate package missing
  swapPkgName(pkg, isSubPackage);

  const { execa } = await import("execa");
  const abortController = new AbortController();
  const proc = execa(CLI_ENTRY_PATH, cliArgs, {
    cwd: __dirname,
    reject: false,
    gracefulCancel: true,
    cancelSignal: abortController.signal,
  });

  proc.stdin.setDefaultEncoding("utf8");

  proc.stdout.on("data", (chunk) => {
    console.log(`  stdout: ${chunk.toString()}`);
  });

  return new Promise((resolve) => {
    const timeout = setTimeout(() => {
      console.log("  timeout: killing process");
      proc.kill();
    }, 60000);

    const prompt = "Would you like to install";
    let hasLogMessage = false;
    let hasPrompt = false;
    let hasPassed = false;

    proc.stderr.on("data", (chunk) => {
      const data = stripVTControlCharacters(chunk.toString());

      console.log(`  stderr: ${data}`);

      if (data.includes(logMessage)) {
        hasLogMessage = true;
      }

      if (data.includes(prompt)) {
        hasPrompt = true;
      }

      if (hasLogMessage && hasPrompt) {
        abortController.abort();
        hasPassed = true;
      }
    });

    proc.on("exit", () => {
      swapPkgName(`.${pkg}`, isSubPackage);
      clearTimeout(timeout);
      resolve(hasPassed);
    });

    proc.on("error", () => {
      swapPkgName(`.${pkg}`, isSubPackage);
      clearTimeout(timeout);
      resolve(false);
    });
  });
};

const runTestStdout = async ({ packageName, cliArgs, logMessage, isSubPackage, cwd } = {}) => {
  // Simulate package missing
  swapPkgName(packageName, isSubPackage);

  const { execa } = await import("execa");
  const abortController = new AbortController();
  const proc = execa(CLI_ENTRY_PATH, cliArgs, {
    cwd: cwd || __dirname,
    reject: false,
    gracefulCancel: true,
    cancelSignal: abortController.signal,
  });

  proc.stdin.setDefaultEncoding("utf8");

  return new Promise((resolve) => {
    const timeout = setTimeout(() => {
      console.log("  timeout: killing process");
      proc.kill();
    }, 60000);

    let hasPassed = false;

    proc.stdout.on("data", (chunk) => {
      const data = stripVTControlCharacters(chunk.toString());

      console.log(`  stdout: ${data}`);

      if (data.includes(logMessage)) {
        hasPassed = true;
        abortController.abort();
      }
    });

    proc.stderr.on("data", (chunk) => {
      const data = stripVTControlCharacters(chunk.toString());
      console.log(`  stderr: ${data}`);
    });

    proc.on("exit", () => {
      swapPkgName(`.${packageName}`, isSubPackage);
      clearTimeout(timeout);
      resolve(hasPassed);
    });

    proc.on("error", () => {
      swapPkgName(`.${packageName}`, isSubPackage);
      clearTimeout(timeout);
      resolve(false);
    });
  });
};

const runTestStdoutWithInput = async ({
  packageName,
  cliArgs,
  inputs,
  logMessage,
  isSubPackage,
} = {}) => {
  // Simulate package missing
  swapPkgName(packageName, isSubPackage);

  const { execa } = await import("execa");
  const abortController = new AbortController();
  const proc = execa(CLI_ENTRY_PATH, cliArgs, {
    cwd: __dirname,
    reject: false,
    gracefulCancel: true,
    cancelSignal: abortController.signal,
  });

  proc.stdin.setDefaultEncoding("utf8");

  return new Promise((resolve) => {
    const timeout = setTimeout(() => {
      console.log("  timeout: killing process");
      proc.kill();
    }, 300000);

    let hasPassed = false;

    proc.stdout.on("data", (chunk) => {
      const data = stripVTControlCharacters(chunk.toString());
      console.log(`  stdout: ${data}`);

      if (data.includes(logMessage)) {
        hasPassed = true;
        abortController.abort();
      }

      for (const input of Object.keys(inputs)) {
        if (data.includes(input)) {
          proc.stdin.write(inputs[input]);
        }
      }
    });

    proc.stderr.on("data", (chunk) => {
      const data = stripVTControlCharacters(chunk.toString());
      console.log(`  stderr: ${data}`);
    });

    proc.on("exit", () => {
      swapPkgName(`.${packageName}`, isSubPackage);
      clearTimeout(timeout);
      resolve(hasPassed);
    });

    proc.on("error", () => {
      swapPkgName(`.${packageName}`, isSubPackage);
      clearTimeout(timeout);
      resolve(false);
    });
  });
};

const runTestWithHelp = async (pkg, cliArgs = [], logMessage = undefined, isSubPackage = false) => {
  // Simulate package missing
  swapPkgName(pkg, isSubPackage);

  const { execa } = await import("execa");
  const abortController = new AbortController();
  const proc = execa(CLI_ENTRY_PATH, cliArgs, {
    cwd: __dirname,
    reject: false,
    gracefulCancel: true,
    cancelSignal: abortController.signal,
  });

  proc.stdin.setDefaultEncoding("utf8");
  proc.stdout.on("data", (chunk) => {
    console.log(`  stdout: ${chunk.toString()}`);
  });

  return new Promise((resolve) => {
    const timeout = setTimeout(() => {
      console.log("  timeout: killing process");
      proc.kill();
    }, 30000);

    const undefinedLogMessage = "Can't find and load command";

    let hasLogMessage = false;
    let hasUndefinedLogMessage = false;
    let hasPassed = false;

    proc.stderr.on("data", (chunk) => {
      const data = stripVTControlCharacters(chunk.toString());

      console.log(`  stderr: ${data}`);

      if (data.includes(logMessage)) {
        hasLogMessage = true;
      }

      if (data.includes(undefinedLogMessage)) {
        hasUndefinedLogMessage = true;
      }

      if (hasLogMessage || hasUndefinedLogMessage) {
        hasPassed = true;
        abortController.abort();
      }
    });

    proc.on("exit", () => {
      swapPkgName(`.${pkg}`, isSubPackage);
      clearTimeout(timeout);
      resolve(hasPassed);
    });

    proc.on("error", () => {
      swapPkgName(`.${pkg}`, isSubPackage);
      clearTimeout(timeout);
      resolve(false);
    });
  });
};

module.exports = {
  runTest,
  runTestStdout,
  runTestStdoutWithInput,
  runTestWithHelp,
};
