import { describe, it, expect, vi, beforeEach } from "vitest";
import { prismaMock, resetPrismaMock } from "@/__tests__/helpers/prisma-mock";
import { createRequest, parseJsonResponse } from "@/__tests__/helpers/api-test-utils";
import { TEST_USER, TEST_RESTAURANT } from "@/__tests__/helpers/fixtures";
import { ApiError } from "@/lib/api";

vi.mock("@/lib/auth", () => ({
  getAuthUserId: vi.fn().mockResolvedValue("user-1"),
}));

import { GET, POST, DELETE } from "./route";
import { getAuthUserId } from "@/lib/auth";

const mockCtx = { params: Promise.resolve({}) };

beforeEach(() => {
  resetPrismaMock();
  vi.mocked(getAuthUserId).mockResolvedValue("user-1");
});

describe("GET /api/user/favorite", () => {
  it("returns empty array when no favorites", async () => {
    prismaMock.favorite.findMany.mockResolvedValue([] as never);
    const req = createRequest("/api/user/favorite");
    const res = await GET(req, mockCtx);
    const { data } = await parseJsonResponse(res);
    expect(data).toEqual([]);
  });

  it("returns favorites list", async () => {
    prismaMock.favorite.findMany.mockResolvedValue([
      {
        id: "fav-1",
        userId: "user-1",
        restaurantId: "rest-1",
        restaurantData: JSON.stringify(TEST_RESTAURANT),
        createdAt: new Date("2025-01-01"),
      },
    ] as never);

    const req = createRequest("/api/user/favorite");
    const res = await GET(req, mockCtx);
    const { status, data } = await parseJsonResponse(res);

    expect(status).toBe(200);
    expect(data).toHaveLength(1);
    expect(data[0].id).toBe("fav-1");
    expect(data[0].restaurantData.name).toBe("测试餐厅");
  });
});

describe("POST /api/user/favorite", () => {
  it("adds a favorite", async () => {
    prismaMock.user.findUnique.mockResolvedValue(TEST_USER as never);
    prismaMock.favorite.create.mockResolvedValue({ id: "fav-new" } as never);

    const req = createRequest("/api/user/favorite", {
      method: "POST",
      body: { restaurant: TEST_RESTAURANT },
    });
    const res = await POST(req, mockCtx);
    const { status, data } = await parseJsonResponse(res);

    expect(status).toBe(200);
    expect(data.id).toBe("fav-new");
  });

  it("returns alreadyExists on duplicate", async () => {
    const { Prisma } = await import("@prisma/client");
    prismaMock.user.findUnique.mockResolvedValue(TEST_USER as never);
    prismaMock.favorite.create.mockRejectedValue(
      new Prisma.PrismaClientKnownRequestError("Unique", {
        code: "P2002",
        clientVersion: "5.0.0",
      }),
    );
    prismaMock.favorite.findFirst.mockResolvedValue({ id: "fav-existing" } as never);

    const req = createRequest("/api/user/favorite", {
      method: "POST",
      body: { restaurant: TEST_RESTAURANT },
    });
    const res = await POST(req, mockCtx);
    const { data } = await parseJsonResponse(res);

    expect(data.alreadyExists).toBe(true);
    expect(data.id).toBe("fav-existing");
  });

  it("returns 401 when not authenticated", async () => {
    vi.mocked(getAuthUserId).mockRejectedValueOnce(new ApiError("请先登录", 401));
    const req = createRequest("/api/user/favorite", {
      method: "POST",
      body: { restaurant: TEST_RESTAURANT },
    });
    const res = await POST(req, mockCtx);
    expect(res.status).toBe(401);
  });

  it("returns 400 when no restaurant", async () => {
    const req = createRequest("/api/user/favorite", {
      method: "POST",
      body: {},
    });
    const res = await POST(req, mockCtx);
    expect(res.status).toBe(400);
  });
});

describe("DELETE /api/user/favorite", () => {
  it("deletes a favorite", async () => {
    prismaMock.favorite.findUnique.mockResolvedValue({
      id: "fav-1",
      userId: "user-1",
    } as never);
    prismaMock.favorite.delete.mockResolvedValue({} as never);

    const req = createRequest("/api/user/favorite", {
      method: "DELETE",
      body: { favoriteId: "fav-1" },
    });
    const res = await DELETE(req, mockCtx);
    const { data } = await parseJsonResponse(res);
    expect(data.ok).toBe(true);
  });

  it("returns 404 when favorite not found", async () => {
    prismaMock.favorite.findUnique.mockResolvedValue(null as never);

    const req = createRequest("/api/user/favorite", {
      method: "DELETE",
      body: { favoriteId: "nonexistent" },
    });
    const res = await DELETE(req, mockCtx);
    expect(res.status).toBe(404);
  });

  it("returns 404 when favorite belongs to another user", async () => {
    prismaMock.favorite.findUnique.mockResolvedValue({
      id: "fav-1",
      userId: "other-user",
    } as never);

    const req = createRequest("/api/user/favorite", {
      method: "DELETE",
      body: { favoriteId: "fav-1" },
    });
    const res = await DELETE(req, mockCtx);
    expect(res.status).toBe(404);
  });
});
