import { describe, expect, it, vi } from "vitest";
import {
  fetchInboxLanes,
  matchCandidate,
  dismissCandidates,
  resubmitRejected,
  resubmitRejectedWithDownload,
  exportInboxCsvUrl,
} from "./inbox-api";

const BASE = "http://test.local";

describe("inbox-api", () => {
  it("fetchInboxLanes hits /api/inbox/lanes", async () => {
    const fetchSpy = vi.fn().mockResolvedValue({
      ok: true,
      json: async () => ({ rejected: [], candidates: [], unmatched: [], done_today: [] }),
    });
    vi.stubGlobal("fetch", fetchSpy);
    const lanes = await fetchInboxLanes();
    expect(fetchSpy).toHaveBeenCalledWith(`${BASE}/api/inbox/lanes`);
    expect(lanes.rejected).toEqual([]);
    vi.unstubAllGlobals();
  });

  it("matchCandidate posts to the candidates/{remit_id}/match endpoint", async () => {
    const fetchSpy = vi.fn().mockResolvedValue({ ok: true, json: async () => ({}) });
    vi.stubGlobal("fetch", fetchSpy);
    await matchCandidate("R-1", "C-1");
    expect(fetchSpy).toHaveBeenCalledWith(
      `${BASE}/api/inbox/candidates/R-1/match`,
      expect.objectContaining({
        method: "POST",
        body: JSON.stringify({ claim_id: "C-1" }),
      }),
    );
    vi.unstubAllGlobals();
  });

  it("dismissCandidates posts pairs", async () => {
    const fetchSpy = vi.fn().mockResolvedValue({ ok: true, json: async () => ({}) });
    vi.stubGlobal("fetch", fetchSpy);
    await dismissCandidates([{ claim_id: "C-1", remit_id: "R-1" }]);
    expect(fetchSpy).toHaveBeenCalledWith(
      `${BASE}/api/inbox/candidates/dismiss`,
      expect.objectContaining({
        method: "POST",
        body: JSON.stringify({ pairs: [{ claim_id: "C-1", remit_id: "R-1" }] }),
      }),
    );
    vi.unstubAllGlobals();
  });

  it("resubmitRejected posts claim_ids", async () => {
    const fetchSpy = vi.fn().mockResolvedValue({
      ok: true,
      json: async () => ({ ok: true, resubmitted: ["C-1"], conflicts: [] }),
    });
    vi.stubGlobal("fetch", fetchSpy);
    const r = await resubmitRejected(["C-1"]);
    expect(fetchSpy).toHaveBeenCalledWith(
      `${BASE}/api/inbox/rejected/resubmit`,
      expect.objectContaining({ method: "POST" }),
    );
    expect(r.resubmitted).toEqual(["C-1"]);
    vi.unstubAllGlobals();
  });

  it("exportInboxCsvUrl returns the export endpoint with the lane", () => {
    expect(exportInboxCsvUrl("rejected")).toBe(`${BASE}/api/inbox/export.csv?lane=rejected`);
  });

  it("resubmitRejectedWithDownload posts to ?download=true and returns blob + filename", async () => {
    // The download path returns a Blob (zip) plus the suggested filename
    // from Content-Disposition, and surfaces per-claim serialize errors
    // via X-Cyclone-Serialize-Errors (JSON-encoded array).
    const zipBytes = new Uint8Array([0x50, 0x4b, 0x03, 0x04]); // PK header
    const fetchSpy = vi.fn().mockResolvedValue({
      ok: true,
      blob: async () => new Blob([zipBytes], { type: "application/zip" }),
      headers: {
        get: (name: string) => {
          const lower = name.toLowerCase();
          if (lower === "content-disposition") {
            return 'attachment; filename="resubmit-3-claims.zip"';
          }
          if (lower === "x-cyclone-serialize-errors") {
            return JSON.stringify([{ claim_id: "C2", reason: "no raw_json" }]);
          }
          return null;
        },
      },
    });
    vi.stubGlobal("fetch", fetchSpy);

    const out = await resubmitRejectedWithDownload(["C1", "C2", "C3"]);
    expect(fetchSpy).toHaveBeenCalledWith(
      `${BASE}/api/inbox/rejected/resubmit?download=true`,
      expect.objectContaining({
        method: "POST",
        body: JSON.stringify({ claim_ids: ["C1", "C2", "C3"] }),
      }),
    );
    expect(out.filename).toBe("resubmit-3-claims.zip");
    expect(out.blob).toBeInstanceOf(Blob);
    expect(out.serializeErrors).toEqual([
      { claim_id: "C2", reason: "no raw_json" },
    ]);

    vi.unstubAllGlobals();
  });

  it("resubmitRejectedWithDownload surfaces non-2xx as an Error", async () => {
    const fetchSpy = vi.fn().mockResolvedValue({
      ok: false,
      status: 400,
      statusText: "Bad Request",
      text: async () => "claim_ids required",
      headers: { get: () => null },
    });
    vi.stubGlobal("fetch", fetchSpy);

    await expect(resubmitRejectedWithDownload([])).rejects.toThrow(/400/);
    vi.unstubAllGlobals();
  });
});
