import { join, relative } from "path";
import { readdir } from "fs/promises";

import { exists } from "../helpers/fs-exists";
import { parseYAML } from "../helpers/parse-yaml";
import { exitWithMsg } from "../helpers/exit-with-msg";

import { SealService } from "../typings/seal-service";
import { RunServiceOptions } from "../typings/run-service-options";

import { validateSealService } from "../validations/seal-service";

import Local from "../runners/local";
import Docker from "../runners/docker";
import { find, isEmpty } from "lodash";
import { validateSeal } from "../validations/seal";
import { Seal } from "../typings/seal";
import { closestMatch } from "closest-match";
import chalk from "chalk";
import * as os from "os";
import { readfile } from "../helpers/fs-readfile-json";
import { writefile } from "../helpers/fs-writefile";
import { execute } from "../helpers/execute";
import getStore from "../helpers/get-store";

export async function getAndValidate(serviceName: string) {
  const _yamlPath = join("seal.yaml");
  if (!(await exists(_yamlPath))) {
    await exitWithMsg(`> "${_yamlPath}" doesn't exists`);
  }

  const _yamlContent: Seal = await validateSeal(await parseYAML(_yamlPath));
  if (
    !_yamlContent ||
    !_yamlContent.services ||
    isEmpty(_yamlContent.services)
  ) {
    await exitWithMsg(`> "seal.yaml" services does not exists`);
  }

  if (!_yamlContent.services[serviceName]) {
    const closestWord = closestMatch(
      serviceName,
      Object.keys(_yamlContent.services),
    );
    console.log(
      chalk.bgRed(`\nUnknown service: "${serviceName}".`),
      chalk.cyan(`Did you mean "${closestWord}"?`),
    );
    await exitWithMsg("");
  }

  return { _yamlPath, _yamlContent };
}

export async function getAndValidateService(
  serviceName: string,
  _yamlContent: any,
) {
  // if service doesn't exists, exit
  const servicePath: string = join(
    process.cwd(),
    _yamlContent.services[serviceName].path,
  );

  if (!(await exists(servicePath))) {
    await exitWithMsg(`> service ${relative(".", servicePath)} doesn't exists`);
  }

  // check if given service has a seal.service.yaml file
  const _serviceYamlPath: string | boolean = await exists(
    join(servicePath, "seal.service.yaml"),
  );
  const _ymlPath: string | boolean = await exists(
    join(servicePath, "seal.service.yaml"),
  );

  // if yaml doesn't exists, exit
  if (!_serviceYamlPath && !_ymlPath) {
    await exitWithMsg(
      `> service ${relative(
        ".",
        join(servicePath, "seal.service.yaml"),
      )} file doesn't exists`,
    );
  }

  const yamlPath: string = (
    _serviceYamlPath ? _serviceYamlPath : _ymlPath
  ) as string;
  const content: SealService = await validateSealService(
    await parseYAML(yamlPath),
  );

  return { servicePath, _serviceYamlPath, _ymlPath, yamlPath, content };
}

async function checkIfAnotherProjectIsUp(_yamlContent: any) {
  const _sealFolderPath = join(os.homedir(), ".seal");
  const files: string[] = await readdir(_sealFolderPath);
  for await (const file of files) {
    if (!file.endsWith(".json") || file === "projects.json") {
      continue;
    }

    if (file !== `${_yamlContent.project_id}.json`) {
      const data: any[] = (await readfile(join(_sealFolderPath, file))) || [];
      const serviceUp = find(data, { status: "up" });
      if (serviceUp) {
        await exitWithMsg(
          `> seal project with ID: ${file.split(".")[0]} is already up`,
        );
      }
    }
  }
}

async function checkIfAlreadyUp(_yamlContent: any, serviceName: string) {
  const store = await getStore();
  const data = store.get("services") || [];
  const service = find(data, { service: serviceName });
  if (service && service.status === "up") {
    await exitWithMsg(
      `> "${serviceName}" service is already up on ${service.platform}`,
    );
  }
}

async function generateEnv() {
  const args: string[] = ["env:generate"];

  console.log(chalk.gray("$ seal", args.join(" ")));

  await execute("seal", args, {
    cwd: process.cwd(),
    shell: true,
  });
}

export default async (
  serviceName: string,
  options: RunServiceOptions,
): Promise<void> => {
  const { platform } = options;
  const { _yamlPath, _yamlContent } = await getAndValidate(serviceName);

  await checkIfAlreadyUp(_yamlContent, serviceName);

  const { servicePath, _serviceYamlPath, _ymlPath, yamlPath, content } =
    await getAndValidateService(serviceName, _yamlContent);

  // if service doesn't contain given platform, exit
  if (!content.platforms[platform]) {
    await exitWithMsg(
      `> service ${serviceName}: "${relative(
        ".",
        join(servicePath, "seal.service.yaml"),
      )}" doesn't support ${platform} platform`,
    );
  }

  const { envfile, build, ports, volumes, context } = content.platforms[platform];

  // generates .env
  await generateEnv();

  switch (platform) {
    case "docker":
      await Docker.start(
        content.container_name,
        servicePath,
        build,
        ports || [],
        envfile,
        volumes,
      );
      break;
    case "local":
      await Local.start(context || servicePath, build);
      break;
  }

  const store = await getStore();
  const data = store.get("services") || [];
  let found = false;
  const json = {
    service: serviceName,
    status: "up",
    platform: platform,
    port: ports,
  };
  data.map((service: any, index: number): any => {
    if (service.service === serviceName) {
      found = true;
      data[index] = json;
    }
  });
  if (!found) {
    data.push(json);
  }
  store.set("services", data);

  console.log(
    chalk.green(`\n"${serviceName}" service is up on ${platform} platform\n`),
  );

  store.save();
};
