import _ from 'lodash';
import logger from './logger';

import type {AppiumDriver} from './appium';
import type {ExternalDriver} from '@appium/types';

const ALL_DRIVERS_MATCH = '*';
const FEATURE_NAME_SEPARATOR = ':';

/**
 * Configures insecure features according to the values in `args.relaxedSecurityEnabled`,
 * `args.allowInsecure`, and `args.denyInsecure`, and informs the user about any
 * globally-applied features.
 * Uses `logger` instead of `this.log` to reduce user confusion.
 */
export function configureGlobalFeatures(this: AppiumDriver) {
  if (this.args.relaxedSecurityEnabled) {
    logger.info(
      `Enabling relaxed security. All insecure features will be ` +
        `enabled unless explicitly disabled by --deny-insecure`,
    );
    this.relaxedSecurityEnabled = true;
  } else if (!_.isEmpty(this.args.allowInsecure)) {
    this.allowInsecure = validateFeatures(this.args.allowInsecure);
    const globalAllowedFeatures = filterInsecureFeatures(this.allowInsecure);
    if (!_.isEmpty(globalAllowedFeatures)) {
      logger.info('Explicitly enabling insecure features:');
      globalAllowedFeatures.forEach((a) => logger.info(`    ${a}`));
    }
  }
  if (_.isEmpty(this.args.denyInsecure)) {
    return;
  }
  this.denyInsecure = validateFeatures(this.args.denyInsecure);
  const globalDeniedFeatures = filterInsecureFeatures(this.denyInsecure);
  if (_.isEmpty(globalDeniedFeatures)) {
    return;
  }
  logger.info('Explicitly disabling insecure features:');
  globalDeniedFeatures.forEach((a) => logger.info(`    ${a}`));
}

/**
 * If anything in the umbrella driver's insecure feature configuration applies to this driver,
 * assign it to the driver instance
 *
 * @param driver
 * @param driverName
 */
export function configureDriverFeatures(
  this: AppiumDriver,
  driver: ExternalDriver,
  driverName: string,
) {
  if (this.relaxedSecurityEnabled) {
    this.log.info(
      `Enabling relaxed security for this session as per the server configuration. ` +
        `All insecure features will be enabled unless explicitly disabled by --deny-insecure`,
    );
    driver.relaxedSecurityEnabled = true;
  }
  const allowedDriverFeatures = filterInsecureFeatures(this.allowInsecure, driverName);
  if (!_.isEmpty(allowedDriverFeatures)) {
    this.log.info('Explicitly enabling insecure features for this session ' +
      'as per the server configuration:',
    );
    allowedDriverFeatures.forEach((a) => this.log.info(`    ${a}`));
    driver.allowInsecure = allowedDriverFeatures;
  }
  const deniedDriverFeatures = filterInsecureFeatures(this.denyInsecure, driverName);
  if (_.isEmpty(deniedDriverFeatures)) {
    return;
  }
  this.log.info('Explicitly disabling insecure features for this session ' +
    'as per the server configuration:',
  );
  deniedDriverFeatures.forEach((a) => this.log.info(`    ${a}`));
  driver.denyInsecure = deniedDriverFeatures;
}

/**
 * Validates the list of allowed/denied server features
 *
 * @param features
 */
function validateFeatures(features: string[]): string[] {
  const validator = (fullName: string) => {
    const errMsg = `The full feature name must include both the destination automation name or the ` +
      `'${ALL_DRIVERS_MATCH}' wildcard to apply the feature to all installed drivers, and ` +
      `the feature name split by a colon. Got '${fullName}' instead`;

    const separatorPos = fullName.indexOf(FEATURE_NAME_SEPARATOR);
    if (separatorPos < 0) {
      throw new Error(errMsg);
    }
    const [automationName, featureName] = [
      fullName.substring(0, separatorPos),
      fullName.substring(separatorPos + 1)
    ];
    if (!automationName || !featureName) {
      throw new Error(errMsg);
    }
    return fullName;
  };
  return features.map(validator);
}

/**
 * Filters the list of insecure features to only those that are
 * applicable to the given driver name.
 * Assumes that all feature names have already been validated
 *
 * @param features
 * @param driverName
 */
function filterInsecureFeatures(
  features: string[],
  driverName: string = ALL_DRIVERS_MATCH
): string[] {
  const filterFn = (fullName: string) => {
    const separatorPos = fullName.indexOf(FEATURE_NAME_SEPARATOR);
    if (separatorPos <= 0) {
      return false;
    }
    const automationName = fullName.substring(0, separatorPos);
    return [driverName, ALL_DRIVERS_MATCH].includes(automationName);
  };
  return features.filter(filterFn);
}
