import { describe, it, expect, vi, beforeEach } from "vitest";
import { maakJsonbSyncRoute } from "./jsonb-sync-route";
import { maakServiceClient } from "@/lib/supabase/server";

vi.mock("@/lib/supabase/server", () => ({
  maakServiceClient: vi.fn(),
}));

interface Item {
  id: string;
  naam: string;
}

function maakRequest(method: string, url: string, body?: unknown): Request {
  return new Request(url, {
    method,
    headers: body ? { "Content-Type": "application/json" } : undefined,
    body: body !== undefined ? JSON.stringify(body) : undefined,
  });
}

describe("maakJsonbSyncRoute", () => {
  beforeEach(() => {
    vi.mocked(maakServiceClient).mockReset();
  });

  describe("zonder Supabase-configuratie", () => {
    beforeEach(() => {
      vi.mocked(maakServiceClient).mockReturnValue(null);
    });

    it("GET geeft lege items en persistent: false", async () => {
      const { GET } = maakJsonbSyncRoute<Item>("tabel");
      const res = await GET();
      const json = await res.json();
      expect(json).toEqual({ items: [], persistent: false });
    });

    it("POST negeert de aanvraag", async () => {
      const { POST } = maakJsonbSyncRoute<Item>("tabel");
      const res = await POST(maakRequest("POST", "http://x/tabel", { id: "1", naam: "a" }));
      const json = await res.json();
      expect(json).toEqual({ genegeerd: true });
    });

    it("DELETE negeert de aanvraag", async () => {
      const { DELETE } = maakJsonbSyncRoute<Item>("tabel");
      const res = await DELETE(maakRequest("DELETE", "http://x/tabel?id=1"));
      const json = await res.json();
      expect(json).toEqual({ genegeerd: true });
    });
  });

  describe("met Supabase-configuratie", () => {
    function maakSbStub() {
      const upsert = vi.fn().mockResolvedValue({ error: null });
      const eq = vi.fn().mockResolvedValue({ error: null });
      const order = vi.fn();
      const select = vi.fn(() => ({ order }));
      const deleteFn = vi.fn(() => ({ eq }));
      const from = vi.fn(() => ({ select, upsert, delete: deleteFn }));
      return { from, select, order, upsert, eq, deleteFn };
    }

    it("GET geeft de data-kolom van elke rij terug als items, persistent: true", async () => {
      const sb = maakSbStub();
      sb.order.mockResolvedValue({
        data: [{ data: { id: "1", naam: "a" } }, { data: { id: "2", naam: "b" } }],
        error: null,
      });
      vi.mocked(maakServiceClient).mockReturnValue(sb as never);

      const { GET } = maakJsonbSyncRoute<Item>("tabel");
      const res = await GET();
      const json = await res.json();
      expect(sb.from).toHaveBeenCalledWith("tabel");
      expect(json).toEqual({
        items: [{ id: "1", naam: "a" }, { id: "2", naam: "b" }],
        persistent: true,
      });
    });

    it("GET geeft 500 bij een Supabase-fout", async () => {
      const sb = maakSbStub();
      sb.order.mockResolvedValue({ data: null, error: { message: "boom" } });
      vi.mocked(maakServiceClient).mockReturnValue(sb as never);

      const { GET } = maakJsonbSyncRoute<Item>("tabel");
      const res = await GET();
      expect(res.status).toBe(500);
      expect(await res.json()).toEqual({ fout: "boom" });
    });

    it("POST upsert't het item als {id, data} en geeft het id terug", async () => {
      const sb = maakSbStub();
      vi.mocked(maakServiceClient).mockReturnValue(sb as never);

      const { POST } = maakJsonbSyncRoute<Item>("tabel");
      const item: Item = { id: "1", naam: "a" };
      const res = await POST(maakRequest("POST", "http://x/tabel", item));
      const json = await res.json();

      expect(sb.upsert).toHaveBeenCalledWith(
        expect.objectContaining({ id: "1", data: item })
      );
      expect(json).toEqual({ opgeslagen: "1" });
    });

    it("GET filtert rijen zonder geldig id-veld uit de teruggegeven items", async () => {
      const sb = maakSbStub();
      sb.order.mockResolvedValue({
        data: [
          { data: { id: "1", naam: "a" } },
          { data: { naam: "geen id" } },
          { data: { id: "", naam: "leeg id" } },
        ],
        error: null,
      });
      vi.mocked(maakServiceClient).mockReturnValue(sb as never);

      const { GET } = maakJsonbSyncRoute<Item>("tabel");
      const res = await GET();
      const json = await res.json();
      expect(json).toEqual({ items: [{ id: "1", naam: "a" }], persistent: true });
    });

    it("POST geeft 400 zonder de Supabase-call uit te voeren als 'id' ontbreekt", async () => {
      const sb = maakSbStub();
      vi.mocked(maakServiceClient).mockReturnValue(sb as never);

      const { POST } = maakJsonbSyncRoute<Item>("tabel");
      const res = await POST(maakRequest("POST", "http://x/tabel", { naam: "zonder id" }));
      expect(res.status).toBe(400);
      expect(sb.upsert).not.toHaveBeenCalled();
    });

    it("POST geeft 400 bij ongeldige JSON", async () => {
      const sb = maakSbStub();
      vi.mocked(maakServiceClient).mockReturnValue(sb as never);

      const { POST } = maakJsonbSyncRoute<Item>("tabel");
      const res = await POST(new Request("http://x/tabel", { method: "POST" }));
      expect(res.status).toBe(400);
    });

    it("POST geeft 500 bij een Supabase-fout", async () => {
      const sb = maakSbStub();
      sb.upsert.mockResolvedValue({ error: { message: "boom" } });
      vi.mocked(maakServiceClient).mockReturnValue(sb as never);

      const { POST } = maakJsonbSyncRoute<Item>("tabel");
      const res = await POST(maakRequest("POST", "http://x/tabel", { id: "1", naam: "a" }));
      expect(res.status).toBe(500);
    });

    it("DELETE verwijdert op basis van 'id' en geeft het id terug", async () => {
      const sb = maakSbStub();
      vi.mocked(maakServiceClient).mockReturnValue(sb as never);

      const { DELETE } = maakJsonbSyncRoute<Item>("tabel");
      const res = await DELETE(maakRequest("DELETE", "http://x/tabel?id=42"));
      const json = await res.json();

      expect(sb.eq).toHaveBeenCalledWith("id", "42");
      expect(json).toEqual({ verwijderd: "42" });
    });

    it("DELETE geeft 400 zonder 'id'-parameter", async () => {
      const sb = maakSbStub();
      vi.mocked(maakServiceClient).mockReturnValue(sb as never);

      const { DELETE } = maakJsonbSyncRoute<Item>("tabel");
      const res = await DELETE(maakRequest("DELETE", "http://x/tabel"));
      expect(res.status).toBe(400);
    });

    it("DELETE geeft 500 bij een Supabase-fout", async () => {
      const sb = maakSbStub();
      sb.eq.mockResolvedValue({ error: { message: "boom" } });
      vi.mocked(maakServiceClient).mockReturnValue(sb as never);

      const { DELETE } = maakJsonbSyncRoute<Item>("tabel");
      const res = await DELETE(maakRequest("DELETE", "http://x/tabel?id=1"));
      expect(res.status).toBe(500);
    });
  });
});
