import { cac } from "cac";
import { loadConfigs, getServiceNames } from "../config/index.mjs";
import {
  parseJobsOption,
  parseShardOption,
  RESERVED,
  resolveCliSelection,
  validateFrameworkOption,
} from "./args.mjs";
import * as runner from "../runner/index.mjs";
import * as runtime from "../runtime/index.mjs";

export function run() {
  const cli = cac("testkit");

  cli
    .command("runtime <action>", "Install or inspect the consumer runtime bundle")
    .option("--dir <path>", "Explicit product directory")
    .option("--path <path>", "Target runtime path relative to the product directory")
    .option("--strict", "Exit non-zero when runtime status is missing or drifted")
    .action((action, options) => {
      if (!["install", "status", "update"].includes(action)) {
        throw new Error('Unknown runtime action. Expected one of: install, status, update.');
      }

      if (action === "status") {
        const status = runtime.getRuntimeStatus(options);
        console.log(runtime.formatRuntimeStatus(status));
        if (options.strict && status.status !== "installed") {
          process.exitCode = 1;
        }
        return;
      }

      const result = runtime.installRuntime(options);
      console.log(
        `Installed testkit runtime to ${result.relativeRuntimeDir} (${result.files.length} file${result.files.length === 1 ? "" : "s"})`
      );
    });

  cli
    .command("[first] [second] [third]", "Run test suites (int, e2e, dal, all)")
    .option("-s, --suite <name>", "Run specific suite(s)", { default: [] })
    .option("--dir <path>", "Explicit product directory")
    .option("--jobs <n>", "Number of isolated worker stacks for the whole run", {
      default: "1",
    })
    .option("--shard <i/n>", "Run only shard i of n at suite granularity")
    .option("--framework <name>", "Filter by framework (k6, playwright, all)", {
      default: "all",
    })
    .action(async (first, second, third, options) => {
      // Resolve: service filter, suite type, and --dir.
      //
      // From product dir:
      //   testkit                          → all services, all types
      //   testkit int -s health            → all services, int, health
      //   testkit api int                  → one service, int
      //   testkit api                      → one service, all types
      //
      // From workspace root:
      //   testkit --dir my-product int     → all services, int
      //   testkit --dir my-product api int → one service, int

      // Now resolve service vs type from remaining args
      const serviceNames = new Set(getServiceNames(options.dir));
      const { service, type } = resolveCliSelection({
        first,
        second,
        serviceNames,
      });

      const allConfigs = loadConfigs({ dir: options.dir });
      const configs = service
        ? allConfigs.filter((config) => config.name === service)
        : allConfigs;
      if (service && configs.length === 0) {
        const available = allConfigs.map((config) => config.name).join(", ");
        throw new Error(`Service "${service}" not found. Available: ${available}`);
      }

      // Lifecycle commands
      if (type === "status" || type === "destroy") {
        for (const config of configs) {
          if (configs.length > 1) console.log(`\n── ${config.name} ──`);
          if (type === "status") runner.showStatus(config);
          else await runner.destroy(config);
        }
        return;
      }

      validateFrameworkOption(options.framework);

      const jobs = parseJobsOption(options.jobs);
      const shard = parseShardOption(options.shard);

      const suiteType = type || "all";
      const suiteNames = Array.isArray(options.suite) ? options.suite : [options.suite].filter(Boolean);
      await runner.runAll(
        configs,
        suiteType,
        suiteNames,
        {
          ...options,
          jobs,
          shard,
        },
        allConfigs
      );
    });

  cli.help();
  cli.parse();
}
