import { afterEach, describe, expect, it, vi } from "vitest";
import { createLiveScout } from "./m5Repos";

function envelope(data: unknown, code = 102000, status = 200): Response {
  return new Response(JSON.stringify({ code, message: "ok", data, error: null }), {
    status,
    headers: { "Content-Type": "application/json" },
  });
}

afterEach(() => {
  vi.unstubAllGlobals();
});

describe("live Scout run repository", () => {
  it("maps run pagination and filters without client sorting", async () => {
    const fetchMock = vi.fn().mockResolvedValue(
      envelope({
        list: [
          {
            id: "run-new",
            job_id: "job-new",
            theme_key: "theme|market",
            theme_label: "週末市集",
            intent: "市集",
            mode: "activity",
            target_count: 8,
            status: "succeeded",
            searched_count: 20,
            duplicate_count: 4,
            irrelevant_count: 8,
            eligible_count: 8,
            pending_count: 3,
            shortfall_count: 0,
            shortfall_reasons: [],
            created_at: 1700000000000000000,
          },
        ],
        pagination: { page: 2, pageSize: 10, total: 11, totalPages: 2 },
      }),
    );
    vi.stubGlobal("fetch", fetchMock);
    const page = await createLiveScout().listRuns({
      page: 2,
      pageSize: 10,
      brandId: "brand/1",
      mode: "activity",
    });
    expect(page.list[0].id).toBe("run-new");
    expect(page.list[0].duplicate_count).toBe(4);
    expect(page.pagination).toEqual({ page: 2, pageSize: 10, total: 11, totalPages: 2 });
    expect(fetchMock.mock.calls[0][0]).toBe(
      "/api/v1/scout/runs?page=2&pageSize=10&brand_id=brand%2F1&mode=activity",
    );
  });

  it("maps independent result pagination and preserves unknown posted time", async () => {
    const fetchMock = vi.fn().mockResolvedValue(
      envelope({
        run: {
          id: "run-1",
          job_id: "job-1",
          intent: "市集",
          mode: "activity",
          status: "succeeded",
          target_count: 8,
          shortfall_reasons: ["relevance_exhausted", "invalid_reason"],
          created_at: 100,
        },
        list: [
          { id: "post-known", run_id: "run-1", text: "市集", posted_at: 200, created_at: 300 },
          { id: "post-unknown", run_id: "run-1", text: "市集", created_at: 100 },
        ],
        pagination: { page: 3, pageSize: 8, total: 17, totalPages: 3 },
      }),
    );
    vi.stubGlobal("fetch", fetchMock);
    const page = await createLiveScout().listRunPosts("run/1", { page: 3, pageSize: 8 });
    expect(page.run.id).toBe("run-1");
    expect(page.run.shortfall_reasons).toEqual(["relevance_exhausted"]);
    expect(page.list[0].posted_at).toBe(200);
    expect(page.list[1].posted_at).toBeUndefined();
    expect(page.pagination.total).toBe(17);
    expect(fetchMock.mock.calls[0][0]).toBe("/api/v1/scout/runs/run%2F1/posts?page=3&pageSize=8");
  });

  it("deletes an encoded run and propagates HTTP errors", async () => {
    const fetchMock = vi
      .fn()
      .mockResolvedValueOnce(envelope({ ok: true }))
      .mockResolvedValueOnce(envelope(null, 500001, 500));
    vi.stubGlobal("fetch", fetchMock);
    const repo = createLiveScout();
    await repo.removeRun("run/1");
    expect(fetchMock.mock.calls[0][0]).toBe("/api/v1/scout/runs/run%2F1");
    expect(fetchMock.mock.calls[0][1]).toMatchObject({ method: "DELETE" });
    await expect(repo.listRuns({ page: 1, pageSize: 10 })).rejects.toThrow();
  });
});
