import { describe, it, expect, vi, beforeEach } from "vitest";
import { NextRequest } from "next/server";

const mockSession = { user: { id: "user-1" } };

vi.mock("@/lib/api-auth", () => ({
  requireDeveloper: vi.fn(),
}));

vi.mock("@/lib/validate-webhook-url", () => ({
  validateWebhookUrl: vi.fn().mockResolvedValue(null),
}));

const { mockSelectChain, mockDeleteChain, mockUpdateChain } = vi.hoisted(() => {
  const mockSelectChain = {
    from: vi.fn().mockReturnThis(),
    where: vi.fn().mockReturnThis(),
    limit: vi.fn().mockResolvedValue([{ id: "wh-1" }]),
  };
  const mockDeleteChain = { where: vi.fn().mockResolvedValue(undefined) };
  const mockUpdateChain = { set: vi.fn().mockReturnThis(), where: vi.fn().mockResolvedValue(undefined) };
  return { mockSelectChain, mockDeleteChain, mockUpdateChain };
});

vi.mock("@epicure/db", () => ({
  db: {
    select: vi.fn(() => mockSelectChain),
    delete: vi.fn(() => mockDeleteChain),
    update: vi.fn(() => mockUpdateChain),
  },
  webhooks: { id: "id", userId: "user_id", url: "url", events: "events", active: "active", createdAt: "created_at" },
  eq: vi.fn((a, b) => ({ a, b, op: "eq" })),
  and: vi.fn((...args) => ({ args, op: "and" })),
}));

const { requireDeveloper } = await import("@/lib/api-auth");
const { validateWebhookUrl } = await import("@/lib/validate-webhook-url");
import { DELETE, PATCH } from "../route";

function makeRequest(method: string, body?: unknown) {
  return new NextRequest("http://localhost/api/v1/webhooks/wh-1", {
    method,
    headers: { "Content-Type": "application/json" },
    body: body ? JSON.stringify(body) : undefined,
  });
}

const ctx = { params: Promise.resolve({ id: "wh-1" }) };

beforeEach(() => {
  vi.clearAllMocks();
  vi.mocked(requireDeveloper).mockResolvedValue({ session: mockSession as never, response: null });
  vi.mocked(validateWebhookUrl).mockResolvedValue(null);
  mockSelectChain.limit.mockResolvedValue([{ id: "wh-1" }]);
});

describe("DELETE /api/v1/webhooks/[id]", () => {
  it("returns 404 when the webhook does not belong to the caller", async () => {
    mockSelectChain.limit.mockResolvedValue([]);
    const res = await DELETE(makeRequest("DELETE"), ctx);
    expect(res.status).toBe(404);
  });

  it("returns 204 on successful deletion", async () => {
    const res = await DELETE(makeRequest("DELETE"), ctx);
    expect(res.status).toBe(204);
  });
});

describe("PATCH /api/v1/webhooks/[id]", () => {
  it("returns 404 when the webhook does not belong to the caller", async () => {
    mockSelectChain.limit.mockResolvedValue([]);
    const res = await PATCH(makeRequest("PATCH", { active: false }), ctx);
    expect(res.status).toBe(404);
  });

  it("returns 400 when the new URL fails SSRF validation", async () => {
    vi.mocked(validateWebhookUrl).mockResolvedValue("Webhook URL must not point to a private or reserved address");
    const res = await PATCH(makeRequest("PATCH", { url: "http://169.254.169.254/" }), ctx);
    expect(res.status).toBe(400);
  });

  it("returns 400 when there are no fields to update", async () => {
    const res = await PATCH(makeRequest("PATCH", {}), ctx);
    expect(res.status).toBe(400);
  });

  it("returns 200 and applies the update", async () => {
    const res = await PATCH(makeRequest("PATCH", { active: false }), ctx);
    expect(res.status).toBe(200);
    expect(mockUpdateChain.set).toHaveBeenCalledWith(expect.objectContaining({ active: false }));
  });
});
