import { describe, expect, test } from "bun:test";
import { readFileSync } from "node:fs";
import { join } from "node:path";
import { createPrometheusAdapter, type FetchFn } from "./prometheus";

const FIXTURE_DIR = join(import.meta.dir, "../../test/fixtures/prometheus");

function loadFixture(name: string): Record<string, unknown> {
  return JSON.parse(readFileSync(join(FIXTURE_DIR, name), "utf8"));
}

function fixtureFetch(fixture: Record<string, unknown>): FetchFn {
  return (async (input: string | URL) => {
    const url = new URL(String(input));
    const query = url.searchParams.get("query") ?? "";
    const body = fixture[query];
    if (body === undefined) {
      throw new Error(`unmocked prometheus query: ${query}`);
    }
    return new Response(JSON.stringify(body), { status: 200 });
  }) as FetchFn;
}

function downFetch(): FetchFn {
  return (async () => {
    throw new Error("connect ECONNREFUSED 127.0.0.1:9090");
  }) as FetchFn;
}

const HOST = "test-host";

describe("createPrometheusAdapter", () => {
  test("emits a host panel with no alert items when everything is nominal", async () => {
    const adapter = createPrometheusAdapter({
      fetchFn: fixtureFetch(loadFixture("normal.json")),
      hostname: HOST,
    });
    const result = await adapter.poll();

    expect(result.items).toEqual([]);
    expect(result.panels).toHaveLength(1);
    expect(result.panels[0]?.id).toBe(`host:${HOST}`);
    const data = result.panels[0]?.data as Record<string, unknown>;
    expect(data.prochot).toBe(false);
    expect(data.memRunwayEtaSeconds).toBe(999999);
    expect(data.diskRootFreePercent).toBeCloseTo(11.05, 1);
  });

  test("PROCHOT: package throttle rate > 0 fires an alert item", async () => {
    const adapter = createPrometheusAdapter({
      fetchFn: fixtureFetch(loadFixture("prochot-firing.json")),
      hostname: HOST,
    });
    const result = await adapter.poll();

    const alerts = result.items.filter((i) => i.kind === "alert");
    expect(alerts).toHaveLength(1);
    expect(alerts[0]?.title).toBe("PROCHOT active");
    expect(alerts[0]?.severity).toBe("act");
    const data = result.panels[0]?.data as Record<string, unknown>;
    expect(data.prochot).toBe(true);
  });

  test("PROCHOT: zero throttle rate does not fire", async () => {
    const adapter = createPrometheusAdapter({
      fetchFn: fixtureFetch(loadFixture("normal.json")),
      hostname: HOST,
    });
    const result = await adapter.poll();
    expect(result.items.filter((i) => i.title === "PROCHOT active")).toHaveLength(0);
  });

  test("mem runway: eta below 30 minutes fires an alert item", async () => {
    const adapter = createPrometheusAdapter({
      fetchFn: fixtureFetch(loadFixture("mem-runway-low.json")),
      hostname: HOST,
    });
    const result = await adapter.poll();

    const alerts = result.items.filter((i) => i.kind === "alert");
    expect(alerts).toHaveLength(1);
    expect(alerts[0]?.title).toBe("Memory runway low");
    expect(alerts[0]?.detail).toContain("900s");
  });

  test("mem runway: eta at the 30 minute boundary does not fire", async () => {
    const adapter = createPrometheusAdapter({
      fetchFn: fixtureFetch(loadFixture("mem-runway-clear-boundary.json")),
      hostname: HOST,
    });
    const result = await adapter.poll();
    expect(result.items.filter((i) => i.title === "Memory runway low")).toHaveLength(0);
  });

  test("mem runway: sentinel 999999 (no exhaustion trajectory) never fires", async () => {
    const adapter = createPrometheusAdapter({
      fetchFn: fixtureFetch(loadFixture("normal.json")),
      hostname: HOST,
    });
    const result = await adapter.poll();
    expect(result.items.filter((i) => i.title === "Memory runway low")).toHaveLength(0);
  });

  test("disk: free percent below 5% fires an alert item", async () => {
    const adapter = createPrometheusAdapter({
      fetchFn: fixtureFetch(loadFixture("disk-low.json")),
      hostname: HOST,
    });
    const result = await adapter.poll();

    const alerts = result.items.filter((i) => i.kind === "alert");
    expect(alerts).toHaveLength(1);
    expect(alerts[0]?.title).toBe("Disk space low");
  });

  test("disk: free percent at the 5% boundary does not fire", async () => {
    const adapter = createPrometheusAdapter({
      fetchFn: fixtureFetch(loadFixture("disk-clear-boundary.json")),
      hostname: HOST,
    });
    const result = await adapter.poll();
    expect(result.items.filter((i) => i.title === "Disk space low")).toHaveLength(0);
  });

  test("panel carries build.slice job count from the real systemd unit_state metric", async () => {
    const adapter = createPrometheusAdapter({
      fetchFn: fixtureFetch(loadFixture("build-slice-jobs.json")),
      hostname: HOST,
    });
    const result = await adapter.poll();
    const data = result.panels[0]?.data as Record<string, unknown>;
    expect(data.buildSliceJobCount).toBe(3);
  });

  test("Prometheus down: poll rejects so the scheduler marks the panel stale, no items produced", async () => {
    const adapter = createPrometheusAdapter({
      fetchFn: downFetch(),
      hostname: HOST,
    });
    await expect(adapter.poll()).rejects.toThrow();
  });

  test("Prometheus reachable but errors (HTTP 500): poll rejects", async () => {
    const adapter = createPrometheusAdapter({
      fetchFn: (async () => new Response("boom", { status: 500 })) as FetchFn,
      hostname: HOST,
    });
    await expect(adapter.poll()).rejects.toThrow();
  });
});
