import { readFile } from "node:fs/promises";
import { prisma } from "../db.js";
import { OrganizationAgentConfiguration } from "../agent/configuration.js";
import { readSkillStoreRoot, verifyStoredSkill } from "../agent/skillStore.js";
import { readSiloOrganizationId } from "./silo.js";

async function main(argv: readonly string[]): Promise<void> {
  const [command, ...args] = argv;
  if (command === undefined || command === "help" || command === "--help") {
    printHelp();
    return;
  }
  const options = parseOptions(args);
  const organizationId = required(options, "organization");
  const siloOrganizationId = readSiloOrganizationId();
  if (organizationId !== siloOrganizationId) {
    throw new Error(`Silo Agent configuration is restricted to ${siloOrganizationId}`);
  }
  const configuration = new OrganizationAgentConfiguration(prisma, readSkillStoreRoot());

  switch (command) {
    case "install-skill": {
      const installed = await configuration.installSkill({
        organizationId,
        sourceDir: required(options, "source"),
        version: required(options, "version"),
      });
      console.log(JSON.stringify(installed));
      return;
    }
    case "upsert-role": {
      const systemPromptFile = options.get("system-prompt-file");
      const toolsJson = options.get("tools-json");
      const tools = toolsJson === undefined
        ? undefined
        : toolsJson === "null"
          ? null
          : parseTools(toolsJson);
      const sortOrderRaw = options.get("sort-order");
      const role = await configuration.upsertRole({
        organizationId,
        roleId: required(options, "role"),
        label: required(options, "label"),
        ...(options.has("model") ? { defaultModel: options.get("model") ?? null } : {}),
        ...(systemPromptFile !== undefined
          ? { systemPrompt: await readFile(systemPromptFile, "utf8") }
          : {}),
        ...(tools !== undefined ? { tools } : {}),
        ...(sortOrderRaw !== undefined ? { sortOrder: integer(sortOrderRaw, "sort-order") } : {}),
        ...(options.has("default") ? { isDefault: boolean(options.get("default")!, "default") } : {}),
      });
      console.log(JSON.stringify(role));
      return;
    }
    case "set-role-skills": {
      const skills = required(options, "skills").split(",").map((name) => name.trim()).filter(Boolean);
      await configuration.setRoleSkills({
        organizationId,
        roleId: required(options, "role"),
        skillNames: skills,
      });
      console.log(JSON.stringify({ roleId: required(options, "role"), skills }));
      return;
    }
    case "list": {
      const roles = await prisma.organizationAgentRole.findMany({
        where: { organizationId },
        orderBy: [{ sortOrder: "asc" }, { roleId: "asc" }],
        include: {
          skillBindings: {
            orderBy: [{ sortOrder: "asc" }, { agentSkillId: "asc" }],
            include: { skill: { select: { name: true, version: true, disabledAt: true } } },
          },
        },
      });
      console.log(JSON.stringify(roles.map((role) => ({
        roleId: role.roleId,
        label: role.label,
        defaultModel: role.defaultModel,
        systemPromptConfigured: role.systemPrompt !== null,
        tools: role.tools,
        disabled: role.disabledAt !== null,
        default: role.isDefault,
        skills: role.skillBindings.map((binding) => ({
          name: binding.skill.name,
          version: binding.skill.version,
          disabled: binding.skill.disabledAt !== null,
        })),
      })), null, 2));
      return;
    }
    case "verify-store": {
      const skills = await prisma.organizationAgentSkill.findMany({
        where: { organizationId, disabledAt: null },
        select: { name: true, contentDigest: true },
      });
      for (const skill of skills) {
        await verifyStoredSkill({ storeRoot: readSkillStoreRoot(), ...skill });
      }
      console.log(JSON.stringify({ verifiedSkills: skills.length }));
      return;
    }
    default:
      throw new Error(`unknown Agent configuration command: ${command}`);
  }
}

function parseOptions(args: readonly string[]): Map<string, string> {
  const options = new Map<string, string>();
  for (let index = 0; index < args.length; index += 2) {
    const flag = args[index];
    const value = args[index + 1];
    if (flag === undefined || !flag.startsWith("--") || value === undefined) {
      throw new Error(`expected --name value, got: ${args.slice(index).join(" ")}`);
    }
    const name = flag.slice(2);
    if (options.has(name)) throw new Error(`duplicate option: --${name}`);
    options.set(name, value);
  }
  return options;
}

function required(options: ReadonlyMap<string, string>, name: string): string {
  const value = options.get(name)?.trim();
  if (value === undefined || value === "") throw new Error(`--${name} is required`);
  return value;
}

function parseTools(raw: string): readonly string[] {
  const parsed = JSON.parse(raw) as unknown;
  if (!Array.isArray(parsed) || parsed.some((value) => typeof value !== "string")) {
    throw new Error("--tools-json must be null or a JSON string array");
  }
  return parsed;
}

function integer(raw: string, name: string): number {
  const value = Number(raw);
  if (!Number.isSafeInteger(value)) throw new Error(`--${name} must be an integer`);
  return value;
}

function boolean(raw: string, name: string): boolean {
  if (raw === "true") return true;
  if (raw === "false") return false;
  throw new Error(`--${name} must be true or false`);
}

function printHelp(): void {
  console.log(`Usage:
  agent-config install-skill --organization ORG --source DIR --version VERSION
  agent-config upsert-role --organization ORG --role ID --label LABEL [--model MODEL] [--system-prompt-file FILE] [--tools-json JSON] [--sort-order N] [--default true|false]
  agent-config set-role-skills --organization ORG --role ID --skills name,name
  agent-config list --organization ORG
  agent-config verify-store --organization ORG`);
}

main(process.argv.slice(2))
  .catch((error) => {
    console.error(error instanceof Error ? error.message : String(error));
    process.exitCode = 1;
  })
  .finally(async () => {
    await prisma.$disconnect();
  });
