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_WEEKEND_PLAN } from "@/__tests__/helpers/fixtures";

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

vi.mock("@/lib/blindbox", () => ({
  requireMembership: vi.fn().mockResolvedValue({}),
}));

vi.mock("@/lib/blindboxPlanGen", () => ({
  runPlanGeneration: vi.fn().mockResolvedValue({
    id: "plan-1",
    days: [{ date: "周六", items: [] }],
    createdAt: new Date(),
  }),
}));

import { POST, PATCH, GET } from "./route";

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

beforeEach(() => {
  resetPrismaMock();
});

describe("POST /api/blindbox/plan", () => {
  it("generates a weekend plan", async () => {
    const req = createRequest("/api/blindbox/plan", {
      method: "POST",
      body: {
        roomId: "bb-room-1",
        availableTime: { date: "2025-03-01", startHour: 9, endHour: 18 },
      },
    });
    const res = await POST(req, mockCtx);
    const { status, data } = await parseJsonResponse(res);

    expect(status).toBe(200);
    expect(data.id).toBe("plan-1");
  });

  it("returns 400 for invalid available time", async () => {
    const req = createRequest("/api/blindbox/plan", {
      method: "POST",
      body: {
        roomId: "bb-room-1",
        availableTime: { date: "2025-03-01", startHour: 18, endHour: 9 },
      },
    });
    const res = await POST(req, mockCtx);
    expect(res.status).toBe(400);
  });

  it("returns 400 when roomId is missing", async () => {
    const req = createRequest("/api/blindbox/plan", {
      method: "POST",
      body: {
        availableTime: { date: "2025-03-01", startHour: 9, endHour: 18 },
      },
    });
    const res = await POST(req, mockCtx);
    expect(res.status).toBe(400);
  });
});

describe("PATCH /api/blindbox/plan", () => {
  it("accepts a plan", async () => {
    prismaMock.weekendPlan.findUnique.mockResolvedValue({
      ...TEST_WEEKEND_PLAN,
      status: "active",
    } as never);
    prismaMock.weekendPlan.update.mockResolvedValue({} as never);

    const req = createRequest("/api/blindbox/plan", {
      method: "PATCH",
      body: { planId: "plan-1", action: "accept" },
    });
    const res = await PATCH(req, mockCtx);
    const { status, data } = await parseJsonResponse(res);

    expect(status).toBe(200);
    expect(data.ok).toBe(true);
  });

  it("completes an accepted plan", async () => {
    prismaMock.weekendPlan.findUnique.mockResolvedValue({
      ...TEST_WEEKEND_PLAN,
      status: "accepted",
    } as never);
    prismaMock.weekendPlan.update.mockResolvedValue({} as never);

    const req = createRequest("/api/blindbox/plan", {
      method: "PATCH",
      body: { planId: "plan-1", action: "complete" },
    });
    const res = await PATCH(req, mockCtx);
    expect(res.status).toBe(200);
  });

  it("expires an accepted plan", async () => {
    prismaMock.weekendPlan.findUnique.mockResolvedValue({
      ...TEST_WEEKEND_PLAN,
      status: "accepted",
    } as never);
    prismaMock.weekendPlan.update.mockResolvedValue({} as never);

    const req = createRequest("/api/blindbox/plan", {
      method: "PATCH",
      body: { planId: "plan-1", action: "expire" },
    });
    const res = await PATCH(req, mockCtx);
    expect(res.status).toBe(200);
  });

  it("returns 400 when accepting non-active plan", async () => {
    prismaMock.weekendPlan.findUnique.mockResolvedValue({
      ...TEST_WEEKEND_PLAN,
      status: "accepted",
    } as never);

    const req = createRequest("/api/blindbox/plan", {
      method: "PATCH",
      body: { planId: "plan-1", action: "accept" },
    });
    const res = await PATCH(req, mockCtx);
    expect(res.status).toBe(400);
  });

  it("returns 403 when not plan owner", async () => {
    prismaMock.weekendPlan.findUnique.mockResolvedValue({
      ...TEST_WEEKEND_PLAN,
      userId: "other-user",
    } as never);

    const req = createRequest("/api/blindbox/plan", {
      method: "PATCH",
      body: { planId: "plan-1", action: "accept" },
    });
    const res = await PATCH(req, mockCtx);
    expect(res.status).toBe(403);
  });

  it("returns 400 for invalid action", async () => {
    prismaMock.weekendPlan.findUnique.mockResolvedValue(TEST_WEEKEND_PLAN as never);

    const req = createRequest("/api/blindbox/plan", {
      method: "PATCH",
      body: { planId: "plan-1", action: "invalid" },
    });
    const res = await PATCH(req, mockCtx);
    expect(res.status).toBe(400);
  });
});

describe("GET /api/blindbox/plan", () => {
  it("returns latest accepted plan", async () => {
    prismaMock.weekendPlan.findFirst.mockResolvedValue({
      id: "plan-1",
      planData: JSON.stringify({ days: [{ date: "周六", items: [] }] }),
      endTime: null,
      createdAt: new Date(),
    } as never);

    const req = createRequest("/api/blindbox/plan?mode=latest&roomId=bb-room-1");
    const res = await GET(req, mockCtx);
    const { status, data } = await parseJsonResponse(res);

    expect(status).toBe(200);
    expect(data.plan).toBeDefined();
    expect(data.plan.id).toBe("plan-1");
  });

  it("returns null when no plan found", async () => {
    prismaMock.weekendPlan.findFirst.mockResolvedValue(null as never);

    const req = createRequest("/api/blindbox/plan?mode=latest&roomId=bb-room-1");
    const res = await GET(req, mockCtx);
    const { data } = await parseJsonResponse(res);

    expect(data.plan).toBeNull();
  });

  it("returns 400 for invalid mode", async () => {
    const req = createRequest("/api/blindbox/plan?mode=invalid");
    const res = await GET(req, mockCtx);
    expect(res.status).toBe(400);
  });
});
