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

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

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

vi.mock("@/lib/webhooks", () => ({
  dispatchWebhook: vi.fn(),
}));

const { mockPlanFindFirst, mockMemberFindFirst, mockRecipeFindFirst, mockInsertValues, mockDeleteWhere } = vi.hoisted(() => ({
  mockPlanFindFirst: vi.fn(),
  mockMemberFindFirst: vi.fn(),
  mockRecipeFindFirst: vi.fn(),
  mockInsertValues: vi.fn().mockResolvedValue(undefined),
  mockDeleteWhere: vi.fn().mockResolvedValue(undefined),
}));

vi.mock("@epicure/db", () => ({
  db: {
    query: {
      mealPlans: { findFirst: mockPlanFindFirst },
      mealPlanMembers: { findFirst: mockMemberFindFirst },
      recipes: { findFirst: mockRecipeFindFirst },
    },
    insert: vi.fn(() => ({ values: mockInsertValues })),
    delete: vi.fn(() => ({ where: mockDeleteWhere })),
  },
  mealPlans: { id: "id", userId: "user_id" },
  mealPlanMembers: { mealPlanId: "meal_plan_id", userId: "user_id" },
  mealPlanEntries: { id: "id", mealPlanId: "meal_plan_id", day: "day", mealType: "meal_type" },
  recipes: { id: "id", authorId: "author_id", visibility: "visibility" },
  eq: vi.fn((a, b) => ({ a, b, op: "eq" })),
  and: vi.fn((...args) => ({ args, op: "and" })),
  or: vi.fn((...args) => ({ args, op: "or" })),
  ne: vi.fn((a, b) => ({ a, b, op: "ne" })),
  inArray: vi.fn((a, b) => ({ a, b, op: "inArray" })),
}));

const { requireSessionOrApiKey } = await import("@/lib/api-auth");
import { POST, DELETE } from "../route";

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

function makeRequest(method: string, body?: unknown, search = "") {
  return new NextRequest(`http://localhost/api/v1/meal-plans/shared/plan-1/entries${search}`, {
    method,
    headers: { "Content-Type": "application/json" },
    body: body ? JSON.stringify(body) : undefined,
  });
}

beforeEach(() => {
  vi.clearAllMocks();
  vi.mocked(requireSessionOrApiKey).mockResolvedValue({ session: mockSession as never, response: null });
  mockPlanFindFirst.mockResolvedValue({ id: "plan-1", userId: "user-1" });
});

const validBody = { day: "mon", mealType: "dinner", recipeId: "r-1", servings: 2 };

describe("POST /api/v1/meal-plans/shared/[mealPlanId]/entries", () => {
  it("returns 404 when the caller has no access to the plan", async () => {
    mockMemberFindFirst.mockResolvedValue(undefined);
    const res = await POST(makeRequest("POST", validBody), ctx);
    expect(res.status).toBe(404);
  });

  it("returns 403 for a viewer trying to add an entry", async () => {
    mockMemberFindFirst.mockResolvedValue({ role: "viewer" });
    const res = await POST(makeRequest("POST", validBody), ctx);
    expect(res.status).toBe(403);
  });

  it("allows an editor to add an entry", async () => {
    mockMemberFindFirst.mockResolvedValue({ role: "editor" });
    mockRecipeFindFirst.mockResolvedValue({ id: "r-1" });
    const res = await POST(makeRequest("POST", validBody), ctx);
    expect(res.status).toBe(201);
    expect(mockInsertValues).toHaveBeenCalledWith(expect.objectContaining({ mealPlanId: "plan-1", day: "mon" }));
  });

  it("returns 404 when the recipe isn't accessible to the editor", async () => {
    mockMemberFindFirst.mockResolvedValue({ role: "editor" });
    mockRecipeFindFirst.mockResolvedValue(undefined);
    const res = await POST(makeRequest("POST", validBody), ctx);
    expect(res.status).toBe(404);
  });
});

describe("DELETE /api/v1/meal-plans/shared/[mealPlanId]/entries", () => {
  it("returns 403 for a viewer trying to remove an entry", async () => {
    mockMemberFindFirst.mockResolvedValue({ role: "viewer" });
    const res = await DELETE(makeRequest("DELETE", undefined, "?entryId=e1"), ctx);
    expect(res.status).toBe(403);
  });

  it("allows an editor to remove an entry", async () => {
    mockMemberFindFirst.mockResolvedValue({ role: "editor" });
    const res = await DELETE(makeRequest("DELETE", undefined, "?entryId=e1"), ctx);
    expect(res.status).toBe(204);
  });
});
