import { getLogger } from "@log4js-node/log4js-api";
import { IChildLogger } from "@vscode-logging/types";
import * as fsExtra from "fs-extra";
import { IExplorationOptions } from "./ExplorationOptions";
import { IModuleFilter } from "@sap/consume-services-types";
import { messages } from "./i18n/messages";
import { IModuleInfo } from "./IModuleInfo";
import _ = require("lodash");
import path = require("path");

/**
 * Main module which exposes API to explore modules at runtime
 */
export class ModulesExploration {
  private static baseLogger: any = getLogger(ModulesExploration.name);

  private explorationOptions: IExplorationOptions;
  private generator: any;

  constructor(opts: IExplorationOptions, context: any, logger?: IChildLogger) {
    this.generator = _.get(context, "context.generator");
    // Set defaults
    this.explorationOptions = {
      modulesPath: null,
      configPath: null,
      modulesFilter: null, // [filterField: "catalog",filterVal: ["V4"]}],
    };
    _.assign(this.explorationOptions, opts);

    if (logger !== undefined) {
      ModulesExploration.baseLogger = logger.getChildLogger({
        label: ModulesExploration.name,
      });
      ModulesExploration.baseLogger.info(
        "Logger instance was received and updated."
      );
    }
  }

  /**
   * Returns an array of explored modules metadata
   */
  public async getExploredModules(): Promise<IModuleInfo[]> {
    // Filter module according to filters option
    return this._getFilteredModules();
  }

  /**
   * Explores modules metadata and prompt them to the user using the given generator
   */
  public async promptExploredModules(promptMsg?: string): Promise<string> {
    let selectedModule = "";
    const message = promptMsg ? promptMsg : messages.getSelectModuleMessage();
    const modulesList: IModuleInfo[] = this._getFilteredModules();
    const modulesNameList = _.map(modulesList, "name");

    if (_.size(modulesNameList) > 0) {
      const moduleTypePrompt = await this.generator.prompt([
        {
          type: "list",
          name: "selectedModule",
          message,
          choices: modulesNameList,
          default: modulesNameList[0],
        },
      ]);

      // Write selected module path to config file
      const moduleToRun = _.find(modulesList, (gen: any) => {
        return gen.name === moduleTypePrompt.selectedModule;
      });
      if (moduleToRun) {
        selectedModule = path.join(
          this.explorationOptions.modulesPath,
          moduleToRun.type,
          moduleToRun.path
        );
      }
    }
    return selectedModule;
  }

  private _getFilteredModules(): IModuleInfo[] {
    const modulesList: IModuleInfo[] = [];
    const modulesPath = this.explorationOptions.modulesPath;

    if (modulesPath && fsExtra.existsSync(modulesPath)) {
      fsExtra.readdirSync(modulesPath).forEach((moduleName: string) => {
        try {
          // For each module , read module.json file
          // eslint-disable-next-line @typescript-eslint/no-var-requires
          const modulesMetadata = require(path.join(
            modulesPath,
            moduleName,
            "modules.json"
          ));
          _.forIn(
            modulesMetadata,
            (value: any, modulesMetadataType: string) => {
              _.forEach(
                modulesMetadata[modulesMetadataType],
                (moduleMD: any) => {
                  // In case module filter passed as null in options, add all modules
                  if (
                    this.explorationOptions.modulesFilter === null ||
                    (moduleMD && this._passFilter(moduleMD))
                  ) {
                    modulesList.push({
                      type: moduleName,
                      name: moduleMD.name,
                      path: moduleMD.path,
                      entryPoint: moduleMD.entryPoint,
                    });
                  }
                }
              );
            }
          );
        } catch (e) {
          const errorMsg = messages.getNoModuleFoundMessage(modulesPath, e);
          console.log(errorMsg);
          ModulesExploration.baseLogger.debug(
            this._getFilteredModules.name,
            // tslint:disable-next-line
            { modulesPath: modulesPath, status: "failed", error: errorMsg }
          );
        }
      });
    }

    return modulesList;
  }

  private _passFilter(moduleMetadata: any) {
    /*Filtering of modules is implemented as a "white list" model according to the definition below.
          A module will pass the defined filter when for all provided keys in the filter:
             * The module defines the filter key and the value provided by the filter is found also in the module
             * The module defines the filter key with empty value (meaning the module knows about the key
               existence but it is not relevant for it)
        */
    const moduleFeatures: IModuleFilter[] = _.get(
      moduleMetadata,
      "features",
      {}
    );
    let passes = true;
    _.forEach(
      this.explorationOptions.modulesFilter,
      (filter: IModuleFilter) => {
        _.forEach(moduleFeatures, (feature) => {
          if (
            feature.name === filter.name &&
            _.size(_.intersection(filter.value, feature.value)) === 0
          ) {
            passes = false;
          }
        });
      }
    );
    // If all filter matches (true) then return true
    return passes;
  }
}
