import { describe, it, expect, vi, beforeEach } from "vitest";

// Mock Supabase client
const mockGetSession = vi.fn();
vi.mock("@/lib/supabase/client", () => ({
  createClient: () => ({
    auth: {
      getSession: mockGetSession,
    },
  }),
}));

// Must import after mock setup
const { api } = await import("@/lib/api");

describe("ApiClient", () => {
  beforeEach(() => {
    vi.restoreAllMocks();
    localStorage.clear();
    mockGetSession.mockResolvedValue({
      data: { session: { access_token: "test-token-123" } },
    });
  });

  it("constructs correct URL with /api base", async () => {
    const fetchSpy = vi.spyOn(globalThis, "fetch").mockResolvedValue(
      new Response(JSON.stringify({ cases: [], total: 0 }), { status: 200 }),
    );

    await api.get("/cases");

    expect(fetchSpy).toHaveBeenCalledWith(
      "/api/cases",
      expect.objectContaining({ method: "GET" }),
    );
  });

  it("does not double-prefix /api/", async () => {
    const fetchSpy = vi.spyOn(globalThis, "fetch").mockResolvedValue(
      new Response(JSON.stringify({}), { status: 200 }),
    );

    await api.get("/deadlines");

    const url = fetchSpy.mock.calls[0][0] as string;
    expect(url).toBe("/api/deadlines");
    expect(url).not.toContain("/api/api/");
  });

  it("sets Authorization header from Supabase session", async () => {
    const fetchSpy = vi.spyOn(globalThis, "fetch").mockResolvedValue(
      new Response(JSON.stringify({}), { status: 200 }),
    );

    await api.get("/cases");

    const requestInit = fetchSpy.mock.calls[0][1] as RequestInit;
    const headers = requestInit.headers as Record<string, string>;
    expect(headers["Authorization"]).toBe("Bearer test-token-123");
  });

  it("sets X-Tenant-ID header from localStorage", async () => {
    localStorage.setItem("kanzlai_tenant_id", "tenant-uuid-123");

    const fetchSpy = vi.spyOn(globalThis, "fetch").mockResolvedValue(
      new Response(JSON.stringify({}), { status: 200 }),
    );

    await api.get("/cases");

    const requestInit = fetchSpy.mock.calls[0][1] as RequestInit;
    const headers = requestInit.headers as Record<string, string>;
    expect(headers["X-Tenant-ID"]).toBe("tenant-uuid-123");
  });

  it("omits X-Tenant-ID when not in localStorage", async () => {
    const fetchSpy = vi.spyOn(globalThis, "fetch").mockResolvedValue(
      new Response(JSON.stringify({}), { status: 200 }),
    );

    await api.get("/cases");

    const requestInit = fetchSpy.mock.calls[0][1] as RequestInit;
    const headers = requestInit.headers as Record<string, string>;
    expect(headers["X-Tenant-ID"]).toBeUndefined();
  });

  it("omits Authorization when no session", async () => {
    mockGetSession.mockResolvedValue({
      data: { session: null },
    });

    const fetchSpy = vi.spyOn(globalThis, "fetch").mockResolvedValue(
      new Response(JSON.stringify({}), { status: 200 }),
    );

    await api.get("/cases");

    const requestInit = fetchSpy.mock.calls[0][1] as RequestInit;
    const headers = requestInit.headers as Record<string, string>;
    expect(headers["Authorization"]).toBeUndefined();
  });

  it("sends POST with JSON body", async () => {
    const fetchSpy = vi.spyOn(globalThis, "fetch").mockResolvedValue(
      new Response(JSON.stringify({ id: "new-id" }), { status: 201 }),
    );

    const body = { case_number: "TEST/001", title: "Test Case" };
    await api.post("/cases", body);

    const requestInit = fetchSpy.mock.calls[0][1] as RequestInit;
    expect(requestInit.method).toBe("POST");
    expect(requestInit.body).toBe(JSON.stringify(body));
    const headers = requestInit.headers as Record<string, string>;
    expect(headers["Content-Type"]).toBe("application/json");
  });

  it("sends PUT with JSON body", async () => {
    const fetchSpy = vi.spyOn(globalThis, "fetch").mockResolvedValue(
      new Response(JSON.stringify({}), { status: 200 }),
    );

    await api.put("/cases/123", { title: "Updated" });
    const requestInit = fetchSpy.mock.calls[0][1] as RequestInit;
    expect(requestInit.method).toBe("PUT");
  });

  it("sends PATCH with JSON body", async () => {
    const fetchSpy = vi.spyOn(globalThis, "fetch").mockResolvedValue(
      new Response(JSON.stringify({}), { status: 200 }),
    );

    await api.patch("/deadlines/123/complete", {});
    const requestInit = fetchSpy.mock.calls[0][1] as RequestInit;
    expect(requestInit.method).toBe("PATCH");
  });

  it("sends DELETE", async () => {
    const fetchSpy = vi.spyOn(globalThis, "fetch").mockResolvedValue(
      new Response(JSON.stringify({}), { status: 200 }),
    );

    await api.delete("/cases/123");
    const requestInit = fetchSpy.mock.calls[0][1] as RequestInit;
    expect(requestInit.method).toBe("DELETE");
  });

  it("throws ApiError on non-ok response", async () => {
    vi.spyOn(globalThis, "fetch").mockResolvedValue(
      new Response(JSON.stringify({ error: "not found" }), { status: 404 }),
    );

    await expect(api.get("/cases/nonexistent")).rejects.toEqual({
      error: "not found",
      status: 404,
    });
  });

  it("handles 204 No Content response", async () => {
    vi.spyOn(globalThis, "fetch").mockResolvedValue(
      new Response(null, { status: 204 }),
    );

    const result = await api.delete("/appointments/123");
    expect(result).toBeUndefined();
  });

  it("handles error response without JSON body", async () => {
    vi.spyOn(globalThis, "fetch").mockResolvedValue(
      new Response("Internal Server Error", {
        status: 500,
        statusText: "Internal Server Error",
      }),
    );

    await expect(api.get("/broken")).rejects.toEqual({
      error: "Internal Server Error",
      status: 500,
    });
  });
});
