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

import { api, ApiError } from "./client";

afterEach(() => {
  vi.unstubAllGlobals();
});

describe("api client", () => {
  it("uses the single API prefix", async () => {
    const fetchMock = vi.fn().mockResolvedValue(
      new Response(JSON.stringify({ status: "ok" }), {
        status: 200,
        headers: { "Content-Type": "application/json" },
      }),
    );
    vi.stubGlobal("fetch", fetchMock);

    await expect(api.get<{ status: string }>("/health")).resolves.toEqual({ status: "ok" });
    expect(fetchMock).toHaveBeenCalledWith(
      "/api/health",
      expect.objectContaining({ credentials: "same-origin" }),
    );
  });

  it("normalizes safe server errors", async () => {
    vi.stubGlobal(
      "fetch",
      vi.fn().mockResolvedValue(
        new Response(
          JSON.stringify({
            error: { code: "unavailable", message: "暂不可用", request_id: "abc" },
          }),
          { status: 503, headers: { "Content-Type": "application/json" } },
        ),
      ),
    );

    const promise = api.get("/health");
    await expect(promise).rejects.toBeInstanceOf(ApiError);
    await expect(promise).rejects.toMatchObject({
      message: "暂不可用",
      status: 503,
      code: "unavailable",
      requestId: "abc",
    });
  });

  it("adds the readable CSRF cookie to write requests", async () => {
    vi.stubGlobal("document", { cookie: "xiaobai_csrf=csrf-token-123" });
    const fetchMock = vi.fn().mockResolvedValue(
      new Response(JSON.stringify({ message: "ok" }), {
        status: 200,
        headers: { "Content-Type": "application/json" },
      }),
    );
    vi.stubGlobal("fetch", fetchMock);

    await api.patch("/account/password", { value: "changed" });

    const request = fetchMock.mock.calls[0]?.[1] as RequestInit;
    const headers = request.headers as Headers;
    expect(request.method).toBe("PATCH");
    expect(headers.get("X-CSRF-Token")).toBe("csrf-token-123");
    expect(headers.get("Content-Type")).toBe("application/json");
  });

  it("parses split NDJSON events through the single streaming client", async () => {
    const encoder = new TextEncoder();
    const body = new ReadableStream({
      start(controller) {
        controller.enqueue(encoder.encode('{"type":"delta","content":"回'));
        controller.enqueue(encoder.encode('答"}\n{"type":"done"}\n'));
        controller.close();
      },
    });
    vi.stubGlobal("fetch", vi.fn().mockResolvedValue(new Response(body, { status: 200 })));
    const events: Array<{ type: string; content?: string }> = [];

    await api.stream<{ type: string; content?: string }>(
      "/mentors/chat",
      {},
      (event) => events.push(event),
    );

    expect(events).toEqual([
      { type: "delta", content: "回答" },
      { type: "done" },
    ]);
  });
});
