import { describe, expect, it } from "vitest";
import { z } from "zod";
import { withQuery } from "./client";
import {
  envelopeSchema,
  objectInstanceRecordSchema,
  runRecordSchema,
  searchResultSchema,
} from "./schemas";

describe("withQuery", () => {
  it("builds a query string skipping empty values", () => {
    expect(
      withQuery("/api/v1/runs/latest/objects", {
        limit: 50,
        cursor: null,
        type: "roa",
        rejected: false,
        q: "",
        reason: undefined,
      }),
    ).toBe("/api/v1/runs/latest/objects?limit=50&type=roa&rejected=false");
  });

  it("URL-encodes values", () => {
    expect(withQuery("/x", { uri: "rsync://a/b.cer" })).toBe(
      "/x?uri=rsync%3A%2F%2Fa%2Fb.cer",
    );
  });

  it("returns the bare path without params", () => {
    expect(withQuery("/x")).toBe("/x");
    expect(withQuery("/x", {})).toBe("/x");
  });
});

describe("envelope + record schemas", () => {
  const objectInstance = {
    objectInstanceId: "obj-1",
    uri: "rsync://repo.example/ca/roa.roa",
    sha256: "ab".repeat(32),
    objectType: "roa",
    result: "ok",
    repoId: "repo-1",
    ppId: "pp-1",
    sourceSection: "fresh",
    rejected: false,
  };

  it("parses a list envelope and exposes the cursor", () => {
    const parsed = envelopeSchema(objectInstanceRecordSchema.array()).parse({
      data: [objectInstance],
      page: { nextCursor: "r1:0:50", limit: 50 },
      meta: { runId: "run_0002", schemaVersion: 1 },
    });
    expect(parsed.data).toHaveLength(1);
    expect(parsed.page?.nextCursor).toBe("r1:0:50");
  });

  it("accepts a null meta.runId (healthz and other run-less endpoints)", () => {
    const parsed = envelopeSchema(z.object({ status: z.string() })).parse({
      data: { status: "ok" },
      page: null,
      meta: { runId: null, schemaVersion: 1 },
    });
    expect(parsed.data.status).toBe("ok");
    expect(parsed.meta?.runId).toBeNull();
  });

  it("keeps unknown fields (passthrough) for forward compatibility", () => {
    const parsed = objectInstanceRecordSchema.parse({
      ...objectInstance,
      futureField: { nested: true },
    });
    expect((parsed as Record<string, unknown>).futureField).toEqual({ nested: true });
  });

  it("rejects records missing required identity fields", () => {
    expect(() =>
      objectInstanceRecordSchema.parse({ uri: "rsync://x" }),
    ).toThrow();
  });

  it("parses run records with partial counts", () => {
    const run = runRecordSchema.parse({
      runId: "run_0002",
      runSeq: 2,
      counts: { objects: 10 },
    });
    expect(run.counts?.objects).toBe(10);
    expect(run.counts?.vrps).toBeUndefined();
  });

  it("parses the unified search result", () => {
    const result = searchResultSchema.parse({
      objects: [objectInstance],
      repos: [],
      publicationPoints: [],
    });
    expect(result.objects[0]?.objectInstanceId).toBe("obj-1");
  });
});
