import { describe, it, expect, vi, beforeEach } from "vitest";

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

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(),
}));

import { POST } from "./route";
import { runPlanGeneration } from "@/lib/blindboxPlanGen";
import { getAuthUserId } from "@/lib/auth";
import { ApiError } from "@/lib/api";

const mockRunPlan = vi.mocked(runPlanGeneration);

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

async function readStream(response: Response): Promise<string> {
  const reader = response.body!.getReader();
  const decoder = new TextDecoder();
  let text = "";
  while (true) {
    const { done, value } = await reader.read();
    if (done) break;
    text += decoder.decode(value, { stream: true });
  }
  return text;
}

describe("POST /api/blindbox/plan/stream", () => {
  it("streams plan generation with SSE events", async () => {
    mockRunPlan.mockImplementation(async (_roomId, _userId, _at, onProgress) => {
      onProgress?.("正在搜索周边...");
      onProgress?.("正在生成行程...");
      return {
        id: "plan-1",
        days: [{ date: "周六", items: [], summary: "轻松逛吃" }],
        createdAt: "2025-03-01T09:00:00.000Z",
      };
    });

    const req = new Request("http://localhost/api/blindbox/plan/stream", {
      method: "POST",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify({
        roomId: "bb-room-1",
        availableTime: { date: "2025-03-01", startHour: 9, endHour: 18 },
      }),
    });

    const res = await POST(req as never);
    expect(res.status).toBe(200);
    expect(res.headers.get("Content-Type")).toBe("text/event-stream");

    const text = await readStream(res);
    expect(text).toContain("event: status");
    expect(text).toContain("正在搜索周边...");
    expect(text).toContain("event: plan");
    expect(text).toContain("plan-1");
  });

  it("streams error event on generation failure", async () => {
    mockRunPlan.mockRejectedValue(new Error("AI 服务不可用"));

    const req = new Request("http://localhost/api/blindbox/plan/stream", {
      method: "POST",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify({
        roomId: "bb-room-1",
        availableTime: { date: "2025-03-01", startHour: 9, endHour: 18 },
      }),
    });

    const res = await POST(req as never);
    const text = await readStream(res);
    expect(text).toContain("event: error");
    expect(text).toContain("AI 服务不可用");
  });

  it("returns 400 for missing roomId", async () => {
    const req = new Request("http://localhost/api/blindbox/plan/stream", {
      method: "POST",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify({
        availableTime: { date: "2025-03-01", startHour: 9, endHour: 18 },
      }),
    });

    const res = await POST(req as never);
    expect(res.status).toBe(400);
  });

  it("returns 400 for invalid available time", async () => {
    const req = new Request("http://localhost/api/blindbox/plan/stream", {
      method: "POST",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify({
        roomId: "bb-room-1",
        availableTime: { date: "2025-03-01", startHour: 18, endHour: 9 },
      }),
    });

    const res = await POST(req as never);
    expect(res.status).toBe(400);
  });

  it("returns 401 when not authenticated", async () => {
    vi.mocked(getAuthUserId).mockRejectedValueOnce(new ApiError("请先登录", 401));
    const req = new Request("http://localhost/api/blindbox/plan/stream", {
      method: "POST",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify({
        roomId: "bb-room-1",
        availableTime: { date: "2025-03-01", startHour: 9, endHour: 18 },
      }),
    });

    const res = await POST(req as never);
    expect(res.status).toBe(401);
  });
});
