import type { ResourceCeilings, RunnerPolicy } from "./types.js";

export const DEFAULT_RUNNER_POLICY: RunnerPolicy = Object.freeze({
  isolation: Object.freeze({
    network: "none",
    rootFilesystem: "read-only",
    capabilities: Object.freeze([]),
    noNewPrivileges: true,
    identity: Object.freeze({ runAsUser: 65_532, runAsGroup: 65_532, requireNonRoot: true }),
  }),
  resources: Object.freeze({
    cpuTimeMs: 5 * 60_000,
    memoryBytes: 2 * 1024 ** 3,
    swapBytes: 0,
    pids: 256,
    tempBytes: 2 * 1024 ** 3,
    outputBytes: 1024 ** 3,
    outputFiles: 20_000,
    wallTimeMs: 10 * 60_000,
  }),
  cancellation: Object.freeze({
    scope: "cgroup",
    initialSignal: "SIGTERM",
    graceMs: 5_000,
    finalSignal: "SIGKILL",
  }),
  workDirectory: Object.freeze({ perJob: true, cleanup: "every-terminal-state" }),
});

const POSITIVE_RESOURCE_KEYS = [
  "cpuTimeMs",
  "memoryBytes",
  "pids",
  "tempBytes",
  "outputBytes",
  "outputFiles",
  "wallTimeMs",
] as const satisfies readonly (keyof ResourceCeilings)[];

export class InvalidRunnerPolicyError extends Error {
  readonly issues: readonly string[];

  constructor(issues: readonly string[]) {
    super(`Invalid runner policy: ${issues.join("; ")}`);
    this.name = "InvalidRunnerPolicyError";
    this.issues = issues;
  }
}

/** Validates values before they are translated to a container/runtime request. */
export function validateRunnerPolicy(policy: RunnerPolicy): void {
  const issues: string[] = [];
  const { isolation, resources, cancellation } = policy;

  if (!Number.isSafeInteger(isolation.identity.runAsUser) || isolation.identity.runAsUser <= 0) {
    issues.push("runAsUser must be a positive non-root integer");
  }
  if (!Number.isSafeInteger(isolation.identity.runAsGroup) || isolation.identity.runAsGroup <= 0) {
    issues.push("runAsGroup must be a positive non-root integer");
  }
  if (isolation.capabilities.length !== 0) issues.push("all capabilities must be dropped");

  for (const key of POSITIVE_RESOURCE_KEYS) {
    const value = resources[key];
    if (!Number.isSafeInteger(value) || value <= 0) issues.push(`${key} must be a positive safe integer`);
  }
  if (!Number.isSafeInteger(resources.swapBytes) || resources.swapBytes < 0) {
    issues.push("swapBytes must be a non-negative safe integer");
  }
  if (!Number.isSafeInteger(cancellation.graceMs) || cancellation.graceMs < 0) {
    issues.push("graceMs must be a non-negative safe integer");
  }

  if (issues.length > 0) throw new InvalidRunnerPolicyError(issues);
}
