import { describe, it, expect, vi } from "vitest";
import { NextRequest, NextResponse } from "next/server";
import { Prisma } from "@prisma/client";
import { ApiError, requireUserId, apiHandler } from "@/lib/api";

vi.mock("@/lib/prisma", () => ({
  prisma: { user: { findUnique: vi.fn() } },
}));

describe("ApiError", () => {
  it("creates with default 400 status", () => {
    const err = new ApiError("bad request");
    expect(err.message).toBe("bad request");
    expect(err.status).toBe(400);
    expect(err.name).toBe("ApiError");
  });

  it("creates with custom status", () => {
    const err = new ApiError("not found", 404);
    expect(err.status).toBe(404);
  });

  it("is instance of Error", () => {
    expect(new ApiError("test")).toBeInstanceOf(Error);
  });
});

describe("requireUserId", () => {
  it("returns userId when valid string", () => {
    expect(requireUserId("user-123")).toBe("user-123");
  });

  it("throws 401 for empty string", () => {
    expect(() => requireUserId("")).toThrow(ApiError);
    try {
      requireUserId("");
    } catch (e) {
      expect((e as ApiError).status).toBe(401);
    }
  });

  it("throws 401 for null/undefined", () => {
    expect(() => requireUserId(null)).toThrow(ApiError);
    expect(() => requireUserId(undefined)).toThrow(ApiError);
  });

  it("throws 401 for non-string", () => {
    expect(() => requireUserId(123)).toThrow(ApiError);
  });
});

describe("apiHandler", () => {
  const mockCtx = { params: Promise.resolve({}) };

  it("passes through successful responses", async () => {
    const handler = apiHandler(async () =>
      NextResponse.json({ ok: true }),
    );
    const req = new NextRequest("http://localhost/api/test");
    const res = await handler(req, mockCtx);
    const data = await res.json();
    expect(res.status).toBe(200);
    expect(data.ok).toBe(true);
  });

  it("converts ApiError to JSON response", async () => {
    const handler = apiHandler(async () => {
      throw new ApiError("not found", 404);
    });
    const req = new NextRequest("http://localhost/api/test");
    const res = await handler(req, mockCtx);
    const data = await res.json();
    expect(res.status).toBe(404);
    expect(data.error).toBe("not found");
  });

  it("handles Prisma P2002 unique constraint error", async () => {
    const handler = apiHandler(async () => {
      const err = new Prisma.PrismaClientKnownRequestError("Unique constraint", {
        code: "P2002",
        clientVersion: "5.0.0",
      });
      throw err;
    });
    const req = new NextRequest("http://localhost/api/test");
    const res = await handler(req, mockCtx);
    const data = await res.json();
    expect(res.status).toBe(409);
    expect(data.error).toBe("该记录已存在或值已被占用");
  });

  it("handles unknown errors as 500", async () => {
    const consoleSpy = vi.spyOn(console, "error").mockImplementation(() => {});
    const handler = apiHandler(async () => {
      throw new Error("unexpected");
    });
    const req = new NextRequest("http://localhost/api/test");
    const res = await handler(req, mockCtx);
    const data = await res.json();
    expect(res.status).toBe(500);
    expect(data.error).toBe("操作失败，请稍后重试");
    consoleSpy.mockRestore();
  });
});
