import { afterEach, describe, expect, test } from "bun:test";
import { mkdtempSync, writeFileSync } from "node:fs";
import { join } from "node:path";
import { tmpdir } from "node:os";
import {
  ConfigReportSchema,
  JobReportSchema,
  parseReportInput,
  reportConfig,
  reportJob,
  type JobReport,
} from "./report";

const startedJob: JobReport = {
  source: "remote-build",
  host: "debian1",
  key: "job-key",
  mirror: "repo-abc123",
  repo: "owner/repo",
  snapshot: "42",
  argv: ["/usr/bin/bun", "test"],
  attempt: 1,
  stage: "started",
  startedAt: "2026-07-20T00:00:00.000Z",
  timeoutSec: 30,
} as const satisfies JobReport;

describe("report schemas", () => {
  test("job report requires mirror basename and rejects id", () => {
    expect(JobReportSchema.safeParse(startedJob).success).toBe(true);
    expect(JobReportSchema.safeParse({ ...startedJob, mirror: "bad/path" }).success).toBe(false);
    expect(JobReportSchema.safeParse({ ...startedJob, id: "forbidden" }).success).toBe(false);
  });

  test("finished job report requires rc and finishedAt", () => {
    const finished = {
      ...startedJob,
      stage: "finished",
      rc: 0,
      finishedAt: "2026-07-20T00:00:05.000Z",
    };
    expect(JobReportSchema.safeParse(finished).success).toBe(true);
    expect(JobReportSchema.safeParse({ ...finished, rc: undefined }).success).toBe(false);
  });

  test("config report requires override on valid and invalid", () => {
    expect(ConfigReportSchema.safeParse({
      source: "remote-build",
      stage: "valid",
      configPath: "/home/user/.claude/build-remote.json",
      observedAt: "2026-07-20T00:00:00.000Z",
      disabled: false,
    }).success).toBe(false);

    expect(ConfigReportSchema.safeParse({
      source: "remote-build",
      stage: "valid",
      configPath: "/home/user/.claude/build-remote.json",
      observedAt: "2026-07-20T00:00:00.000Z",
      disabled: false,
      override: false,
    }).success).toBe(true);

    expect(ConfigReportSchema.safeParse({
      source: "remote-build",
      stage: "invalid",
      kind: "config-invalid-shape",
      detail: "bad",
      configPath: "/home/user/.claude/build-remote.json",
      preservedPath: "/home/user/.claude/build-remote.json.bad",
      observedAt: "2026-07-20T00:00:00.000Z",
    }).success).toBe(false);
  });

  test("parseReportInput routes by stage union", () => {
    expect(parseReportInput(startedJob).kind).toBe("job");
    expect(parseReportInput({
      source: "remote-build",
      stage: "missing",
      configPath: "/home/user/.claude/build-remote.json",
      observedAt: "2026-07-20T00:00:00.000Z",
    }).kind).toBe("config");
  });
});

describe("report clients", () => {
  const previousFlag = process.env.OVERDECK_SPINE_REPORT;
  const previousConfigDir = process.env.OVERDECK_CONFIG_DIR;

  afterEach(() => {
    if (previousFlag === undefined) delete process.env.OVERDECK_SPINE_REPORT;
    else process.env.OVERDECK_SPINE_REPORT = previousFlag;
    if (previousConfigDir === undefined) delete process.env.OVERDECK_CONFIG_DIR;
    else process.env.OVERDECK_CONFIG_DIR = previousConfigDir;
  });

  test("honors flag-off with zero network calls", async () => {
    delete process.env.OVERDECK_SPINE_REPORT;
    let called = false;
    await reportJob(startedJob, {
      fetcher: async () => {
        called = true;
        return new Response(JSON.stringify({ ok: true }), { status: 200 });
      },
    });
    expect(called).toBe(false);
  });

  test("swallows controller delivery failure", async () => {
    process.env.OVERDECK_SPINE_REPORT = "1";
    const dir = mkdtempSync(join(tmpdir(), "spine-report-"));
    process.env.OVERDECK_CONFIG_DIR = dir;
    writeFileSync(join(dir, "token"), "secret-token", { mode: 0o600 });

    await expect(reportJob(startedJob, {
      fetcher: async () => new Response("nope", { status: 500 }),
    })).resolves.toBeUndefined();

    await expect(reportConfig({
      source: "remote-build",
      stage: "valid",
      configPath: "/home/user/.claude/build-remote.json",
      observedAt: "2026-07-20T00:00:00.000Z",
      disabled: false,
      override: false,
    }, {
      fetcher: async () => {
        throw new Error("network down");
      },
    })).resolves.toBeUndefined();
  });

  test("posts to the expected endpoints when enabled", async () => {
    process.env.OVERDECK_SPINE_REPORT = "1";
    const dir = mkdtempSync(join(tmpdir(), "spine-report-post-"));
    process.env.OVERDECK_CONFIG_DIR = dir;
    writeFileSync(join(dir, "token"), "secret-token", { mode: 0o600 });

    const paths: string[] = [];
    await reportJob(startedJob, {
      baseUrl: "http://controller.test",
      fetcher: async (input, init) => {
        paths.push(String(input));
        expect(init?.headers).toMatchObject({
          authorization: "Bearer secret-token",
        });
        return new Response(JSON.stringify({ ok: true }), { status: 200 });
      },
    });
    await reportConfig({
      source: "remote-build",
      stage: "missing",
      configPath: "/home/user/.claude/build-remote.json",
      observedAt: "2026-07-20T00:00:00.000Z",
    }, {
      baseUrl: "http://controller.test",
      fetcher: async (input) => {
        paths.push(String(input));
        return new Response(JSON.stringify({ ok: true }), { status: 200 });
      },
    });

    expect(paths).toEqual([
      "http://controller.test/jobs/report",
      "http://controller.test/spine/config/report",
    ]);
  });
});
