#!/usr/bin/env node

const fs = require("fs");
const readline = require("readline");
const path = require("path");
const Table = require("cli-table");
const { spawn } = require("child_process");
const yaml = require("js-yaml");
const shell = require("shelljs");
const os = require("os");
const args = process.argv.splice(process.execArgv.length + 2);
const userCommand = args[0];
const rl = readline.createInterface({
  input: process.stdin,
  output: process.stdout,
});

async function processFix() {
  return new Promise((resolve, reject) => {
    const psCommand =
      "ps ax | grep stg-cli/client.js | grep -v grep | awk '{print $1}'";
    const data = spawn("sh", ["-c", psCommand]);

    data.stdout.on("data", async (chunk) => {
      const pids = chunk.toString().trim().split("\n");
      if (pids.length === 0) {
        // console.log("No processes found for client.js");
        stopSpawnProcess(data);
        reject("No processes found for client.js");
        return;
        i;
      }

      // Kill each process found
      for (const pid of pids) {
        killProcess(pid);
      }
      resolve();
    });

    data.stderr.on("data", (chunk) => {
      console.error(`Error finding processes: ${chunk.toString()}`);
      reject(`Error finding processes: ${chunk.toString()}`);
    });

    data.on("error", (error) => {
      console.error(`Error executing command: ${error.message}`);
      reject(`Error executing command: ${error.message}`);
    });

    data.on("exit", (code) => {
      // console.log(`Spawn process exited with code ${code}`);
      resolve();
    });
  });
}

function killProcess(pid) {
  if (pid) {
    process.kill(pid, "SIGTERM");
    console.log(`Service with PID ${pid} stopped successfully.`);
  } else {
    console.log("Service PID not found. The service may not be running.");
  }
}
function stopSpawnProcess(spawnProcess) {
  spawnProcess.kill(); // Kill the spawn process
}
async function initializePackage() {
  const currentDir = process.cwd();
  const configFilePath = path.join(__dirname, "init.json");
  const pidFilePath = path.join(__dirname, "pid.json");
  const nohupFilePath = path.join(__dirname, "nohup.out");

  try {
    let initializedProjects = [];
    let initialized = false;

    // Check if init.json file exists, if not, create it with default values
    if (!fs.existsSync(configFilePath)) {
      fs.writeFileSync(configFilePath, JSON.stringify({ initialized, initializedProjects }, null, 2), "utf-8");
    } else {
      // Read data from init.json
      const data = fs.readFileSync(configFilePath, "utf-8");
      const jsonData = JSON.parse(data);
      initialized = jsonData.initialized || false;
      initializedProjects = jsonData.initializedProjects || [];
    }

    const projectName = path.basename(currentDir);
    if (initializedProjects.includes(projectName)) {
      console.log(`The project '${projectName}' has already been initialized.`);
      process.exit(1);
    }

    const platform = os.platform();
    if (platform !== "win32") {
      try {
        await processFix();
      } catch (error) {
        console.error("Error during processFix:", error);
      }
    }
    const child = spawn("node", [path.join(__dirname, "client.js")], {
      detached: true,
      stdio: [
        "ignore",
        fs.openSync(nohupFilePath, "a"),
        fs.openSync(nohupFilePath, "a"),
      ],
      cwd: currentDir 
    });
    const pid = child.pid.toString();

    initializedProjects.push(projectName);
    fs.writeFileSync(configFilePath, JSON.stringify({ initialized: true, initializedProjects }, null, 2), "utf-8");

    // Update pid.json file with the PID value
    try {
      const data = fs.readFileSync(pidFilePath, "utf-8");
      const existingData = JSON.parse(data);
      existingData.push({ projectName, pid });
      fs.writeFileSync(pidFilePath, JSON.stringify(existingData, null, 2), "utf-8");
    } catch (err) {
      fs.writeFileSync(pidFilePath, JSON.stringify([{ projectName, pid }], null, 2), "utf-8");
    }

    child.unref();
    console.log(`The project '${projectName}' is initialized successfully.`);
    process.exit(1);
  } catch (error) {
    console.error("Error initializing the package:", error);
    process.exit(1);
  }
}

function saveConfiguredApplications(applications) {
  const configAppsFile = path.join(__dirname, "configuredApplications.json");
  const data = JSON.stringify(applications, null, 2);
  try {
    fs.writeFileSync(configAppsFile, data, "utf-8");
    console.log("File written successfully");
  } catch (error) {
    console.error("Error writing to the file:", error);
  }
}

function loadConfiguredApplications() {
  try {
    const data = fs.readFileSync(
      `${__dirname}/configuredApplications.json`,
      "utf-8"
    );
    return JSON.parse(data) || [];
  } catch (error) {
    return [];
  }
}

async function start(appId) {
  console.log("Application ID:", appId);

  let configApps = loadConfiguredApplications();
  if (configApps?.length) {
    const app = configApps.filter((item) => item?.appId == appId);
    if (app?.length) {
      const lastIndexApp = app?.length === 1 ? app[0] : app[app?.length - 1];
      if (lastIndexApp?.status === "active") {
        console.log(
          `The application with appId: ${appId} is already running...`
        );
        rl.close();
      } else {
        lastIndexApp.status = "active";
        saveConfiguredApplications(configApps);
        console.log(`The application with appId: ${appId} is started.`);
      }
    } else {
      console.log("No application found with the appId:", appId);
      rl.close();
    }
  } else {
    console.log("You haven't configured any application yet.");
    rl.close();
  }
}

async function stop(appId) {
  console.log("Application ID:", appId);

  let configApps = loadConfiguredApplications();
  if (configApps?.length) {
    const app = configApps.filter((item) => item.appId == appId);
    if (app?.length) {
      const lastIndexApp = app?.length === 1 ? app[0] : app[app?.length - 1];
      if (lastIndexApp?.status === "stopped") {
        console.log(`The application with appId: ${appId} is already stopped.`);
        rl.close();
      } else {
        lastIndexApp.status = "stopped"; // Update application status to STOPPED
        saveConfiguredApplications(configApps);
        console.log(`The application with appId: ${appId} is stopped.`);
      }
    } else {
      console.log("No application found with the appId:", appId);
      rl.close();
    }
  } else {
    console.log("You haven't configured any application yet.");
    rl.close();
  }
}

function findYmlFiles(jsonPayload) {
  let configApps = loadConfiguredApplications();
  const currentDir = process.cwd();
  const ymlFiles = [];

  function traverseDir(currentDir) {
    let files = fs.readdirSync(currentDir);
    for (const file of files) {
      const filePath = path.join(currentDir, file);
      if (fs.statSync(filePath).isDirectory()) {
        if (path.basename(filePath) !== "node_modules") {
          traverseDir(filePath);
        }
      } else if (file.endsWith("onetab-pipeline.yml")) {
        ymlFiles.push(filePath);
      }
    }
  }

  traverseDir(currentDir);
  console.log("YML files:", ymlFiles);

  if (ymlFiles?.length) {
    const pushEventPayload = JSON.parse(jsonPayload);
    const branchPush = pushEventPayload?.ref.split("/").pop(); // Extracting branch name from the push event payload

    let lastIndexApp = null;
    let filterConfigApps = null;
    let configid = null;
    if (configApps?.length) {
      filterConfigApps = configApps.filter(
        (obj) => obj?.appId == pushEventPayload?.repository?.id
      );
      if (filterConfigApps?.length) {
        lastIndexApp = filterConfigApps[filterConfigApps?.length - 1];
      }
    }

    const configAppsFile = path.join(__dirname, "configuredApplications.json");

    if (
      filterConfigApps?.length === 0 ||
      configApps?.length === 0 ||
      lastIndexApp?.status === "active"
    ) {
      for (const ymlFile of ymlFiles) {
        const fileContent = fs.readFileSync(ymlFile, "utf8");
        const workflowConfig = yaml.load(fileContent);
        const ymlBranch = workflowConfig.on.push.branches[0];
        const app = {
          uuid: null,
          appId: pushEventPayload?.repository?.id,
          appName: pushEventPayload?.repository?.full_name,
          commitId: pushEventPayload?.head_commit?.id,
          status: "active",
          path: ymlFile,
          pushEventPayload,
          branch: branchPush,
        };
        if (
          filterConfigApps?.length === 0 ||
          configApps?.length === 0 ||
          lastIndexApp?.status === "active"
        ) {
          configApps.push(app);
          saveConfiguredApplications(configApps, configAppsFile);
          if (ymlBranch === branchPush) {
            return ymlFiles;
          } else {
            console.log("Branch mismatch");
          }
        } else {
          console.log("condition not match");
        }
      }
    } else {
      console.log("Failed to locate onetab-pipeline yml file.");
    }
  }
}

async function table() {
  console.log("__dirname ------------->", __dirname);
  const configApps = loadConfiguredApplications();
  if (configApps?.length) {
    const header = ["App ID", "App Name", "Status", "Path", "UUID"];
    const table = new Table({
      head: header,
    });

    const tableData = [];
    configApps.forEach((app) => {
      const index = tableData.findIndex((obj) => obj?.appId === app?.appId);
      if (index === -1) {
        table.push([
          app?.appId,
          app?.appName,
          app?.status,
          app?.path,
          app?.uuid,
        ]);
      } else {
        table.splice(index, 1, [
          app?.appId,
          app?.appName,
          app?.status,
          app?.path,
          app?.uuid,
        ]);
      }
      tableData.push({
        appId: app?.appId,
        appName: app?.appName,
        status: app?.status,
        path: app?.path,
        uuid: app?.uuid,
      });
    });
    console.log(table.toString());
    return table.toString();
  }
  console.log("No data to display.");
  return;
}
function processObject(obj, index) {
  const pid = obj.pid;
  if (pid) {
    try {
      process.kill(pid, "SIGTERM");
    } catch (error) {
      console.log("The service has already been killed.");
    }
    console.log(`Service with PID ${pid} stopped successfully.`);
  } else {
    console.log("Service PID not found. The service may not be running.");
  }
}
async function kill(projectName) {
  const pidFilePath = path.join(__dirname, "pid.json");
  const initPath = path.join(__dirname, "init.json");
  const configurePath = path.join(__dirname, "configuredApplications.json");
  const nohupPath = path.join(__dirname, "nohup.out");

  try {
    if (!projectName) {
      const fileContent = fs.existsSync(pidFilePath) ? fs.readFileSync(pidFilePath, "utf-8") : '[]';
      const data = JSON.parse(fileContent);

      data.forEach(processObject);

      [pidFilePath, initPath, configurePath, nohupPath].forEach((filePath) => {
        if (fs.existsSync(filePath)) fs.unlinkSync(filePath);
      });

      console.log(`All services stopped and related files deleted.`);
      return;
    }

    const fileContent = fs.existsSync(pidFilePath) ? fs.readFileSync(pidFilePath, "utf-8") : '[]';
    const data = JSON.parse(fileContent);

    const filteredData = data.filter((obj) => obj.projectName === projectName);

    if (filteredData.length === 0) {
      console.log(`No service found for project '${projectName}'.`);
      return;
    }
    filteredData.forEach((obj) => {
      const pid = obj.pid;
      if (pid) {
        try {
          process.kill(pid, "SIGTERM");
        } catch (error) {
          console.log(`Error killing service with Vanshil PID ${pid}:`, error);
        }
        console.log(`Service with PID ${pid} for project '${projectName}' stopped successfully.`);
      } else {
        console.log(`Service PID not found for project '${projectName}'. The service may not be running.`);
      }
    });

    // Remove the project initialization from init.json
    const initContent = fs.existsSync(initPath) ? fs.readFileSync(initPath, "utf-8") : '{}';
    const initJsonData = JSON.parse(initContent);
    const updatedInitializedProjects = initJsonData.initializedProjects ? initJsonData.initializedProjects.filter((name) => name !== projectName) : [];
    fs.writeFileSync(initPath, JSON.stringify({ initialized: true, initializedProjects: updatedInitializedProjects }, null, 2), "utf-8");


    const configContent = fs.existsSync(configurePath) ? fs.readFileSync(configurePath, "utf-8") : '[]';
    const configJsonData = JSON.parse(configContent);
    const updatedConfigApps = configJsonData.filter((app) => app?.appName !== projectName);
    fs.writeFileSync(configurePath, JSON.stringify(updatedConfigApps, null, 2), "utf-8");

    // Remove the project's PID data from pid.json
    const updatedData = data.filter((obj) => obj.projectName !== projectName);
    fs.writeFileSync(pidFilePath, JSON.stringify(updatedData, null, 2), "utf-8");

    console.log(`Project '${projectName}' has been terminated and its initialization removed.`);
  } catch (error) {
    console.error("Error killing the service:", error);
  }
}



module.exports = { findYmlFiles };

switch (userCommand) {
  case "init":
    initializePackage();
    break;

  case "start":
    start(args[1]);
    process.exit(1);

  case "stop":
    stop(args[1]);
    process.exit(1);

  case "apps":
    table();
    process.exit(1);

  case "kill":
    kill(args[1]);
    process.exit(1);

  case "-help":
    console.info(`
      Usage: stg-cli <command>
      Command List:
        1. stg-cli init 
        2. stg-cli start appId 
        3. stg-cli stop appId 
        4. stg-cli apps 
        5. stg-cli kill
        6. stg-cli -help 
      `);
    process.exit(1);

  default:
    console.log(
      `Run command "stg-cli -help" to see the list of available commands.`
    );
}
