/* eslint-disable no-console */
import {fs} from 'appium/support';
import {main as appiumServer} from 'appium';
import getPort from 'get-port';
// eslint-disable-next-line import/named
import {info, success, warning} from 'log-symbols';
import {exec} from 'teen_process';

const APPIUM_BIN = require.resolve('appium');

/**
 * Creates hooks to install a driver and a plugin and starts an Appium server w/ the given extensions.
 * @param {E2ESetupOpts} opts
 * @returns {void}
 */
export function pluginE2EHarness(opts) {
  let {
    appiumHome,
    before,
    after,
    serverArgs = {},
    driverSource,
    driverPackage,
    driverName,
    driverSpec,
    pluginSource,
    pluginPackage,
    pluginSpec,
    pluginName,
    port,
    host,
  } = opts;

  /**
   * @type {AppiumServer}
   */
  let server;

  // eslint-disable-next-line mocha/no-top-level-hooks
  before(async function () {
    const chai = await import('chai');
    const chaiAsPromised = await import('chai-as-promised');
    chai.use(chaiAsPromised.default);
    chai.should();

    const setupAppiumHome = async () => {
      /**
       * @type {AppiumEnv}
       */
      const env = {...process.env};

      if (appiumHome) {
        env.APPIUM_HOME = appiumHome;
        await fs.mkdirp(appiumHome);
        console.log(`${info} Set \`APPIUM_HOME\` to ${appiumHome}`);
      }

      return env;
    };

    /**
     *
     * @param {AppiumEnv} env
     */
    const installDriver = async (env) => {
      console.log(`${info} Checking if driver "${driverName}" is installed...`);
      const driverListArgs = [APPIUM_BIN, 'driver', 'list', '--json'];
      console.log(`${info} Running: ${process.execPath} ${driverListArgs.join(' ')}`);
      const {stdout: driverListJson} = await exec(process.execPath, driverListArgs, {
        env,
      });
      const installedDrivers = JSON.parse(driverListJson);

      if (!installedDrivers[driverName]?.installed) {
        console.log(`${warning} Driver "${driverName}" not installed; installing...`);
        const driverArgs = [APPIUM_BIN, 'driver', 'install', '--source', driverSource, driverSpec];
        if (driverPackage) {
          driverArgs.push('--package', driverPackage);
        }
        console.log(`${info} Running: ${process.execPath} ${driverArgs.join(' ')}`);
        await exec(process.execPath, driverArgs, {
          env,
        });
      }
      console.log(`${success} Installed driver "${driverName}"`);
    };

    /**
     *
     * @param {AppiumEnv} env
     */
    const installPlugin = async (env) => {
      console.log(`${info} Checking if plugin "${pluginName}" is installed...`);
      const pluginListArgs = [APPIUM_BIN, 'plugin', 'list', '--json'];
      const {stdout: pluginListJson} = await exec(process.execPath, pluginListArgs, {
        env,
      });
      const installedPlugins = JSON.parse(pluginListJson);

      if (!installedPlugins[pluginName]?.installed) {
        console.log(`${warning} Plugin "${pluginName}" not installed; installing...`);
        const pluginArgs = [APPIUM_BIN, 'plugin', 'install', '--source', pluginSource, pluginSpec];
        if (pluginPackage) {
          pluginArgs.push('--package', pluginPackage);
        }
        console.log(`${info} Running: ${process.execPath} ${pluginArgs.join(' ')}`);
        await exec(process.execPath, pluginArgs, {
          env,
        });
      }
      console.log(`${success} Installed plugin "${pluginName}"`);
    };

    const createServer = async () => {
      if (!port) {
        port = await getPort();
      }
      console.log(`${info} Will use port ${port} for Appium server`);
      this.port = port;

      /** @type {import('appium/types').Args} */
      const args = {
        port,
        address: host,
        usePlugins: [pluginName],
        useDrivers: [driverName],
        appiumHome,
        ...serverArgs,
      };
      server = /** @type {AppiumServer} */ (await appiumServer(args));
    };

    const env = await setupAppiumHome();
    await installDriver(env);
    await installPlugin(env);
    await createServer();
  });

  // eslint-disable-next-line mocha/no-top-level-hooks
  after(async function () {
    if (server) {
      await server.close();
    }
  });
}

/**
 * @typedef E2ESetupOpts
 * @property {string} [appiumHome] - Path to Appium home directory
 * @property {Mocha.before} before - Mocha "before all" hook function
 * @property {Mocha.after} after - Mocha "after all" hook function
 * @property {Partial<import('appium/types').Args>} [serverArgs] - Arguments to pass to Appium server
 * @property {import('appium/types').InstallType & string} driverSource - Source of driver to install
 * @property {string} [driverPackage] - Package name of driver to install
 * @property {string} driverName - Name of driver to install
 * @property {string} driverSpec - Spec of driver to install
 * @property {import('appium/types').InstallType & string} pluginSource - Source of plugin to install
 * @property {string} [pluginPackage] - Package name of plugin to install
 * @property {string} pluginSpec - Spec of plugin to install
 * @property {string} pluginName - Name of plugin to install
 * @property {number} [port] - Port to use for Appium server
 * @property {string} [host] - Host to use for Appium server
 */

/**
 * @typedef {import('@appium/types').AppiumServer} AppiumServer
 */

/**
 * @typedef {import('appium/types').AppiumEnv} AppiumEnv
 */
