import { exec } from "child_process";
import fs from "fs";
import fetch from "node-fetch";
import { join } from "path";
import Generator from "./Generator";
import { PACKAGE_JSON_URL } from "../utils/constants";
import sleep from "../utils/sleep";

export default class PackageJsonGenerator extends Generator {
  private readonly localPackageJsonPath = join("output", "package.json");
  private packageJsonData: any;

  constructor(private metaParameters: MetaParameters) {
    super();
  }

  public async run(): Promise<BackendOperationResult> {
    try {
      await this.getPackageJson();
      this.verifyIfNodeToBeCreatedExists();
      this.savePackageJson();
      this.insertCredentialInPackageJson();
      await sleep(1000); // to ensure both insertions succeed
      this.insertNodeInPackageJson();
      return { completed: true };
    } catch (error) {
      return { completed: false, error };
    }
  }

  /**Verify if the service name of the node to be created already exists in the package.json of the official n8n repo.*/
  private verifyIfNodeToBeCreatedExists() {
    const nodeToBeCreated = this.metaParameters.serviceName.replace(/\s/, "");

    for (let node of this.packageJsonData.n8n.nodes) {
      let existingNode = node.match(/dist\/nodes\/(.*)\.node\.js/)[1];

      // remove dir name if it exists
      if (existingNode.split("").includes("/")) {
        existingNode = existingNode.replace(/.*\//, "");
      }

      if (nodeToBeCreated === existingNode) {
        throw Error(
          "The node you are trying to create already exists in the official n8n repo.\nPlease change the serviceName of the node in metaParameters in parameters.ts."
        );
      }
    }
  }

  /**Insert the new node credential at their appropriate location in `package.json`.*/
  private insertCredentialInPackageJson() {
    const command = this.formatCommand(`
    gen updateCredentialPackageJson
      --serviceCredential ${this.getServiceCredentialName(this.metaParameters)}
      --credentialSpot ${this.findCredentialSpot()}
    `);

    exec(command);
  }

  /**Insert the new node at their appropriate location in `package.json`.*/
  private insertNodeInPackageJson() {
    const { serviceName } = this.metaParameters;
    const formattedServiceName = serviceName.replace(/\s/, "");

    const command = this.formatCommand(`
    gen updateNodePackageJson
      --serviceName ${formattedServiceName}
      --nodeSpot ${this.findNodeSpot()}
    `);

    exec(command);
  }

  /**Get contents of `package.json` from `packages/nodes-base` from the official n8n repo.*/
  private async getPackageJson() {
    const response = await fetch(PACKAGE_JSON_URL);
    this.packageJsonData = await response.json();
  }

  /**Write contents of `package.json` from `packages/nodes-base` from official repo in /output dir.*/
  private savePackageJson() {
    fs.writeFileSync(
      this.localPackageJsonPath,
      JSON.stringify(this.packageJsonData, null, 2)
    );
  }

  /**Find the credential right after which the new node credential is to be inserted in `package.json`.*/
  private findCredentialSpot() {
    const serviceCredential = this.getServiceCredentialName(
      this.metaParameters
    );
    for (let credential of this.packageJsonData.n8n.credentials) {
      const relevantString = credential.slice(17);
      if (relevantString[0] < serviceCredential[0]) {
        continue;
      }
      return relevantString.replace(".credentials.js", "");
    }

    throw Error(
      "No spot for node credential path insertion found in package.json retrieved from official n8n repository."
    );
  }

  /**Find the node right after which the new node is to be inserted in `package.json`.*/
  private findNodeSpot() {
    for (let node of this.packageJsonData.n8n.nodes) {
      const pathMatch = node.match(/dist\/nodes\/(.*)\.node\.js/);

      if (pathMatch === null) {
        throw Error(
          "No path match found in package.json retrieved from official n8n repository."
        );
      }

      const relevantString = pathMatch[1];

      if (relevantString[0] < this.metaParameters.serviceName[0]) {
        continue;
      }
      return relevantString.replace(".node.js", "");
    }
    throw Error(
      "No spot for node path insertion found in package.json retrieved from official n8n repository."
    );
  }
}