import {
  authenticateWithOtp,
  checkRunning,
  getFlagValue,
  hasFlag,
  printAuthCommand,
  printerHostInfo,
  setLogLevel,
  startWaiting,
  stopWaiting,
} from "./commandlineutils.js";
import { VALID_LOG_LEVELS } from "./logging.js";
import chalk from "chalk";

async function getRunningServer(cli, logger) {
  try {
    startWaiting("Checking if Farm Control Server is running...", logger);
    const runningServer = await checkRunning();
    await stopWaiting();
    return runningServer;
  } catch (err) {
    await stopWaiting();
    cli.failure("Failed to check if Farm Control Server is running:", err);
    cli.blank();
    return null;
  }
}

async function checkServerRunning(cli, logger) {
  const runningServer = await getRunningServer(cli, logger);
  if (runningServer == false) {
    cli.failure("Farm Control Server is not running.");
    cli.blank();
    return false;
  }
  if (runningServer == null) {
    return false;
  }
  cli.success("Farm Control Server is running.");
  return runningServer;
}

function printServerInfo(cli, runningServer) {
  const buildNumber = runningServer.buildNumber ?? runningServer.build ?? "n/a";
  cli.blank();
  cli.sectionHeader("FarmControl Server");
  cli.label(
    "Version:",
    runningServer.version ? `v${runningServer.version}` : "n/a",
  );
  cli.label("Build:", buildNumber === "dev" ? "dev" : `b${buildNumber}`);
  cli.label("Config:", runningServer.configPath || "n/a");
  cli.label("Data Dir:", runningServer.dataDir || "n/a");
  cli.label("Connected:", cli.yesNo(runningServer.connected == true));
  cli.label("Authenticated:", cli.yesNo(runningServer.authenticated == true));
  cli.sectionFooter();
  cli.blank();

  if (runningServer.authenticated == false && runningServer.connected == true) {
    cli.sectionHeader("Authenticate");
    printAuthCommand(cli);
    cli.sectionFooter();
  } else if (
    runningServer.connected == true &&
    runningServer.authenticated == true
  ) {
    cli.sectionHeader("Host");
    printerHostInfo(runningServer.host, cli);
    cli.sectionFooter();
  }

  cli.blank();
}

function printHelp(cli) {
  cli.sectionHeader("Help");
  cli.label(
    chalk.bold.white("Usage: ") +
      chalk.bold.cyanBright("farmcontrol-server ") +
      chalk.white.dim("[options]"),
  );
  cli.sectionFooter();
  const optionsPadding = " ".repeat(9);
  cli.label(
    chalk.bold.white("Options: ") +
      chalk.bold.magentaBright("--info") +
      " Outputs server information",
  );
  cli.label(
    optionsPadding +
      chalk.bold.magentaBright("--logLevel") +
      " " +
      chalk.dim("<level>") +
      " Sets log level (" +
      chalk.bold.cyan("trace") +
      ", " +
      chalk.bold.blue("debug") +
      ", " +
      chalk.bold.green("info") +
      ", " +
      chalk.bold.yellow("warn") +
      ", " +
      chalk.bold.red("error") +
      ")",
  );
  cli.label(
    optionsPadding +
      chalk.bold.magentaBright("--otp") +
      " " +
      chalk.dim("<otp>") +
      " Authenticates with OTP",
  );
  cli.sectionFooter();
  cli.blank();
}

async function runOtpCommand(cli, logger, otpCode) {
  cli.blank();

  if (otpCode == undefined) {
    cli.failure("OTP code is required.");
    cli.blank();
    return { handled: true, result: false };
  }

  if (!/^\d{6}$/.test(String(otpCode))) {
    cli.failure("OTP code must be a 6-digit number.");
    cli.blank();
    return { handled: true, result: false };
  }

  startWaiting("Authenticating with OTP...", logger);
  let authenticatedServer;
  try {
    authenticatedServer = await authenticateWithOtp(otpCode);
    await stopWaiting();
  } catch (err) {
    await stopWaiting();
    throw err;
  }

  if (authenticatedServer.valid == false) {
    cli.failure("Failed to authenticate!");
    cli.failure(authenticatedServer.error);
    cli.blank();
    return authenticatedServer;
  }

  cli.success("Authenticated with OTP.");
  cli.blank();
  cli.sectionHeader("Host");
  printerHostInfo(authenticatedServer.host, cli);
  cli.sectionFooter();
  cli.blank();
  return authenticatedServer;
}

async function runLogLevelCommand(cli, logger, level) {
  cli.blank();

  if (level == undefined) {
    cli.failure("Log level is required.");
    cli.blank();
    return { handled: true, result: false };
  }

  const normalized = String(level).toLowerCase();
  if (!VALID_LOG_LEVELS.includes(normalized)) {
    cli.failure(
      `Invalid log level. Must be one of: ${VALID_LOG_LEVELS.join(", ")}`,
    );
    cli.blank();
    return { handled: true, result: false };
  }

  startWaiting("Setting log level...", logger);
  let result;
  try {
    result = await setLogLevel(normalized);
    await stopWaiting();
  } catch (err) {
    await stopWaiting();
    throw err;
  }

  if (result.valid == false) {
    cli.failure("Failed to set log level!");
    cli.failure(result.error);
    cli.blank();
    return result;
  }

  cli.success(`Log level set to ${result.logLevel}.`);
  cli.blank();
  return result;
}

const commands = [
  {
    matches: () => hasFlag("info"),
    run: async ({ cli, logger }) => {
      const runningServer = await checkServerRunning(cli, logger);
      if (runningServer == false) {
        return { handled: true, result: false };
      }
      printServerInfo(cli, runningServer);
      return { handled: true, result: true };
    },
  },
  {
    matches: () => hasFlag("help") || hasFlag("h"),
    run: async ({ cli }) => {
      printHelp(cli);
      return true;
    },
  },
  {
    matches: () =>
      getFlagValue("logLevel") != undefined || hasFlag("logLevel"),
    run: async ({ cli, logger }) => {
      const runningServer = await checkServerRunning(cli, logger);
      if (runningServer == false) {
        return { handled: true, result: false };
      }
      const result = await runLogLevelCommand(
        cli,
        logger,
        getFlagValue("logLevel"),
      );
      return { handled: true, result };
    },
  },
  {
    matches: () => getFlagValue("otp") != undefined || hasFlag("otp"),
    run: async ({ cli, logger }) => {
      const runningServer = await checkServerRunning(cli, logger);
      if (runningServer == false) {
        return { handled: true, result: false };
      }
      const result = await runOtpCommand(cli, logger, getFlagValue("otp"));
      return { handled: true, result };
    },
  },
];

export async function handleCommandLine(cli, logger) {
  const command = commands.find((entry) => entry.matches());

  if (!command) {
    const runningServer = await getRunningServer(cli, logger);
    if (runningServer == null) {
      return { handled: true, result: false };
    }
    return { handled: false, runningServer };
  }

  const result = await command.run({ cli, logger });
  return { handled: true, result };
}
