import { describe, expect, it, vi } from "vitest";
import { createBobbinClient } from "$lib/api/client";
import {
	fetchPeoplePage,
	fetchPinned,
	fetchReposPage,
	fetchStarredPage,
	fetchVouchesPage
} from "./pages";

const enriched = () => Response.json({ output: { items: [], hits: [] }, data: {} });

const requestBody = (fetchMock: ReturnType<typeof vi.fn>, index = 0) => {
	const init = fetchMock.mock.calls[index][1] as RequestInit;
	return JSON.parse(String(init.body)) as {
		xrpc: string;
		enrich: { source: string; type: string; targets?: string[] }[];
	};
};

describe("fetchStarredPage", () => {
	it("only requests minidocs for string URI subjects", async () => {
		const fetchMock = vi.fn<typeof globalThis.fetch>().mockResolvedValue(
			Response.json({
				output: { items: [] },
				data: {}
			})
		);
		const ctx = createBobbinClient({ serviceUrl: "https://bobbin.test", fetch: fetchMock });

		await fetchStarredPage(ctx, { did: "did:plc:alice" });

		const body = requestBody(fetchMock);
		expect(body.enrich[0].targets).toEqual(["items[].value.subject.uri"]);
	});

	it("targets repo stats and owner minidocs independently", async () => {
		const fetchMock = vi
			.fn<typeof globalThis.fetch>()
			.mockResolvedValueOnce(
				Response.json({
					output: {
						items: [
							{
								uri: "at://did:plc:alice/sh.tangled.feed.star/one",
								value: { subject: { did: "did:plc:repo" }, createdAt: "2026-08-01T00:00:00Z" }
							}
						]
					},
					data: {}
				})
			)
			.mockResolvedValueOnce(Response.json({ output: { items: [] }, data: {} }));
		const ctx = createBobbinClient({ serviceUrl: "https://bobbin.test", fetch: fetchMock });

		await fetchStarredPage(ctx, { did: "did:plc:alice" });

		const body = requestBody(fetchMock, 1);
		expect(body.enrich).toEqual([
			{
				source: "sh.tangled.feed.star:subject",
				type: "sh.tangled.query.enrichResponse#count",
				targets: ["items[].value.repoDid"]
			},
			{
				source: "sh.tangled.repo:.repo",
				type: "com.bad-example.identity.miniDoc",
				targets: ["items[].uri"]
			}
		]);
	});

	it.each([
		[undefined, "items[].value.repoDid"],
		["needle", "hits[].value.repoDid"]
	])("targets repo stats for list/search results", async (q, expectedTarget) => {
		const fetchMock = vi.fn<typeof globalThis.fetch>().mockResolvedValue(enriched());
		const ctx = createBobbinClient({ serviceUrl: "https://bobbin.test", fetch: fetchMock });

		await fetchReposPage(ctx, {
			did: "did:plc:alice",
			handle: "alice.test",
			viewerDid: "did:plc:viewer",
			q
		});

		const body = requestBody(fetchMock);
		expect(body.enrich).toHaveLength(2);
		expect(body.enrich.every((descriptor) => descriptor.targets?.[0] === expectedTarget)).toBe(
			true
		);
	});

	it.each([
		["followers" as const, "items[].uri"],
		["following" as const, "items[].value.subject"]
	])("targets %s identities and stats", async (direction, expectedTarget) => {
		const fetchMock = vi.fn<typeof globalThis.fetch>().mockResolvedValue(enriched());
		const ctx = createBobbinClient({ serviceUrl: "https://bobbin.test", fetch: fetchMock });

		await fetchPeoplePage(ctx, {
			did: "did:plc:alice",
			viewerDid: "did:plc:viewer",
			direction
		});

		const body = requestBody(fetchMock);
		expect(body.enrich).toHaveLength(4);
		expect(body.enrich.every((descriptor) => descriptor.targets?.[0] === expectedTarget)).toBe(
			true
		);
	});

	it("does not retry missing enriched identities", async () => {
		const fetchMock = vi.fn<typeof globalThis.fetch>().mockResolvedValue(
			Response.json({
				output: {
					items: [
						{
							uri: "at://did:plc:bob/sh.tangled.graph.follow/one",
							value: { subject: "did:plc:alice", createdAt: "2026-08-01T00:00:00Z" }
						}
					]
				},
				data: {}
			})
		);
		const ctx = createBobbinClient({ serviceUrl: "https://bobbin.test", fetch: fetchMock });

		const page = await fetchPeoplePage(ctx, {
			did: "did:plc:alice",
			direction: "followers"
		});

		expect(fetchMock).toHaveBeenCalledOnce();
		expect(page.items[0].handle).toBe("handle.invalid");
	});

	it("targets incoming vouch authors", async () => {
		const fetchMock = vi.fn<typeof globalThis.fetch>().mockResolvedValue(enriched());
		const ctx = createBobbinClient({ serviceUrl: "https://bobbin.test", fetch: fetchMock });

		await fetchVouchesPage(ctx, {
			did: "did:plc:alice",
			cursors: { outgoing: null }
		});

		expect(requestBody(fetchMock).enrich[0].targets).toEqual(["items[].uri"]);
	});

	it("targets repo dids for both pinned repo queries", async () => {
		const fetchMock = vi.fn<typeof globalThis.fetch>().mockImplementation(async () => enriched());
		const ctx = createBobbinClient({ serviceUrl: "https://bobbin.test", fetch: fetchMock });

		await fetchPinned(ctx, {
			keys: ["did:plc:repo", "at://did:plc:alice/sh.tangled.repo/example"],
			handle: "alice.test",
			viewerDid: "did:plc:viewer"
		});

		const bodies = fetchMock.mock.calls.map((_, index) => requestBody(fetchMock, index));
		expect(bodies.map((body) => body.xrpc).sort()).toEqual([
			"sh.tangled.repo.getRepos",
			"sh.tangled.repo.getReposByRepoDids"
		]);
		expect(
			bodies.every((body) =>
				body.enrich.every((descriptor) => descriptor.targets?.[0] === "items[].value.repoDid")
			)
		).toBe(true);
	});

	it("deduplicates repos returned by fetchReposPage by repoKey", async () => {
		const fetchMock = vi.fn<typeof globalThis.fetch>().mockResolvedValue(
			Response.json({
				output: {
					items: [
						{
							uri: "at://did:plc:alice/sh.tangled.repo/repo-one",
							value: {
								repoDid: "did:plc:repo-one",
								name: "repo-one",
								createdAt: "2026-08-01T00:00:00Z"
							}
						},
						{
							uri: "at://did:plc:alice/sh.tangled.repo/repo-one-dup",
							value: {
								repoDid: "did:plc:repo-one",
								name: "repo-one-dup",
								createdAt: "2026-08-02T00:00:00Z"
							}
						},
						{
							uri: "at://did:plc:alice/sh.tangled.repo/repo-two",
							value: {
								repoDid: "did:plc:repo-two",
								name: "repo-two",
								createdAt: "2026-08-03T00:00:00Z"
							}
						}
					]
				},
				data: {}
			})
		);
		const ctx = createBobbinClient({ serviceUrl: "https://bobbin.test", fetch: fetchMock });

		const result = await fetchReposPage(ctx, {
			did: "did:plc:alice",
			handle: "alice.test",
			viewerDid: "did:plc:viewer"
		});

		expect(result.items).toHaveLength(2);
		expect(result.items[0].repoDid).toBe("did:plc:repo-one");
		expect(result.items[0].name).toBe("repo-one-dup");
		expect(result.items[1].repoDid).toBe("did:plc:repo-two");
	});
});
