import fs from "node:fs";
import path from "node:path";
import { describe, expect, it } from "vitest";

type RootPackageManifest = {
  dependencies?: Record<string, string>;
  pnpm?: {
    overrides?: Record<string, string>;
  };
};

const PI_PACKAGE_NAMES = [
  "@mariozechner/pi-agent-core",
  "@mariozechner/pi-ai",
  "@mariozechner/pi-coding-agent",
  "@mariozechner/pi-tui",
] as const;

function readRootManifest(): RootPackageManifest {
  const manifestPath = path.resolve(process.cwd(), "package.json");
  return JSON.parse(fs.readFileSync(manifestPath, "utf8")) as RootPackageManifest;
}

function isExactPinnedVersion(spec: string): boolean {
  return !spec.startsWith("^") && !spec.startsWith("~");
}

function isPiOverrideKey(key: string): boolean {
  return key.startsWith("@mariozechner/pi-") || key.includes("@mariozechner/pi-");
}

function readPiDependencySpecs() {
  const dependencies = readRootManifest().dependencies ?? {};
  return PI_PACKAGE_NAMES.map((name) => ({
    name,
    spec: dependencies[name],
  }));
}

function expectNoGraphViolations(violations: string[], message: string) {
  expect(violations, message).toEqual([]);
}

describe("pi package graph guardrails", () => {
  it("keeps root Pi packages aligned to the same exact version", () => {
    const specs = readPiDependencySpecs();

    const missing = specs.filter((entry) => !entry.spec).map((entry) => entry.name);
    expectNoGraphViolations(
      missing,
      `Missing required root Pi dependencies: ${missing.join(", ") || "<none>"}. Mixed or incomplete Pi root dependencies create an unsupported package graph.`,
    );

    const presentSpecs = specs.map((entry) => entry.spec);
    const uniqueSpecs = [...new Set(presentSpecs)];
    expect(
      uniqueSpecs,
      `Root Pi dependencies must stay aligned to one exact version. Found: ${specs.map((entry) => `${entry.name}=${entry.spec}`).join(", ")}. Mixed Pi versions create an unsupported package graph.`,
    ).toHaveLength(1);

    const inexact = specs.filter((entry) => !isExactPinnedVersion(entry.spec));
    expectNoGraphViolations(
      inexact.map((entry) => `${entry.name}=${entry.spec}`),
      `Root Pi dependencies must use exact pins, not ranges. Found: ${inexact.map((entry) => `${entry.name}=${entry.spec}`).join(", ") || "<none>"}. Range-based Pi specs can silently create an unsupported package graph.`,
    );
  });

  it("forbids pnpm overrides that target Pi packages", () => {
    const manifest = readRootManifest();
    const overrides = manifest.pnpm?.overrides ?? {};
    const piOverrides = Object.keys(overrides).filter(isPiOverrideKey);

    expectNoGraphViolations(
      piOverrides,
      `pnpm.overrides must not target Pi packages. Found: ${piOverrides.join(", ") || "<none>"}. Pi-specific overrides can silently create an unsupported package graph.`,
    );
  });
});
