/**
 * Tests for `apiFetch` — regression coverage для issue #1219.
 *
 * Bug: `apiFetch` безусловно звал `response.json()` после `response.ok`-check,
 * что падало с `SyntaxError: Unexpected end of JSON input` на 204 No Content
 * (DELETE /api/v1/custom-pois/{id}, DELETE /api/v1/admin/site-finder/weight-
 * profiles/{id}). Промис мутации реджектился → onSuccess/invalidateQueries не
 * срабатывали → UI рассинхронизировался с сервером (объект удалён, но виден).
 *
 * Fix: на 204 / пустом теле resolve'имся с undefined (см. api.ts:apiFetch).
 */
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";

import { apiFetch } from "../api";

// Минимальный mock Response — fetch global под jsdom есть, но контролируем
// status/body точечно через vi.stubGlobal.
function mockResponse(init: {
  status: number;
  body?: string;
  ok?: boolean;
}): Response {
  const { status, body = "", ok = status >= 200 && status < 300 } = init;
  return {
    ok,
    status,
    text: () => Promise.resolve(body),
    json: () =>
      body
        ? Promise.resolve(JSON.parse(body))
        : Promise.reject(new SyntaxError("Unexpected end of JSON input")),
  } as unknown as Response;
}

describe("apiFetch — 204 No Content handling (#1219)", () => {
  beforeEach(() => {
    vi.stubGlobal("fetch", vi.fn());
  });

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

  it("resolves with undefined on 204 No Content (empty body)", async () => {
    vi.mocked(fetch).mockResolvedValueOnce(
      mockResponse({ status: 204, body: "" }),
    );

    const result = await apiFetch<unknown>("/api/v1/custom-pois/42", {
      method: "DELETE",
    });

    expect(result).toBeUndefined();
  });

  it("does not throw SyntaxError on 204 (regression guard)", async () => {
    vi.mocked(fetch).mockResolvedValueOnce(
      mockResponse({ status: 204, body: "" }),
    );

    await expect(
      apiFetch<unknown>("/api/v1/custom-pois/42", { method: "DELETE" }),
    ).resolves.toBeUndefined();
  });

  it("resolves with parsed JSON on 200 OK with body", async () => {
    const payload = { id: 1, name: "Test POI", lat: 56.83, lon: 60.6 };
    vi.mocked(fetch).mockResolvedValueOnce(
      mockResponse({ status: 200, body: JSON.stringify(payload) }),
    );

    const result = await apiFetch<typeof payload>("/api/v1/custom-pois/1");

    expect(result).toEqual(payload);
  });

  it("resolves with undefined on 200 OK with empty body (defensive)", async () => {
    // Не-стандартно (200 + empty), но возможно для некоторых прокси/edge cases.
    // До фикса падало тем же SyntaxError что и 204.
    vi.mocked(fetch).mockResolvedValueOnce(
      mockResponse({ status: 200, body: "" }),
    );

    const result = await apiFetch<unknown>("/api/v1/something");

    expect(result).toBeUndefined();
  });

  it("throws on 4xx with detail body included", async () => {
    vi.mocked(fetch).mockResolvedValueOnce(
      mockResponse({
        status: 404,
        body: JSON.stringify({ detail: "POI not found" }),
        ok: false,
      }),
    );

    await expect(
      apiFetch<unknown>("/api/v1/custom-pois/999", { method: "DELETE" }),
    ).rejects.toThrow(/API error 404/);
  });

  it("throws on 5xx", async () => {
    vi.mocked(fetch).mockResolvedValueOnce(
      mockResponse({
        status: 500,
        body: "Internal Server Error",
        ok: false,
      }),
    );

    await expect(apiFetch<unknown>("/api/v1/foo")).rejects.toThrow(
      /API error 500/,
    );
  });
});
