import { describe, expect, it, vi } from "vitest";
import * as knotmirror from "./knotmirror";

const jsonResponse = (body: unknown): Response =>
	new Response(JSON.stringify(body), {
		status: 200,
		headers: { "content-type": "application/json" }
	});

describe("knotmirror.tree", () => {
	it("fetches the readme blob", async () => {
		const fetchMock = vi.fn<typeof globalThis.fetch>().mockImplementation(async (input) => {
			const url = new URL(String(input));
			if (url.pathname.endsWith("getTree")) {
				return jsonResponse({
					ref: "main",
					files: [
						{
							name: "README.md",
							mode: "100644",
							size: 12,
							last_commit: {
								hash: "abc",
								message: "docs",
								when: "2026-07-01T00:00:00Z"
							}
						}
					]
				});
			}
			return new Response("# hello", {
				status: 200,
				headers: { "content-type": "text/plain" }
			});
		});

		const ctx = knotmirror.createKnotMirrorClient("https://mirror.test", fetchMock);
		const tree = await knotmirror.tree(ctx, {
			repo: "did:plc:repo",
			ref: "main",
			path: "docs"
		});

		expect(tree.readme).toEqual({ filename: "README.md", contents: "# hello" });
		expect(tree.files[0]).toMatchObject({ name: "README.md", mode: "100644" });
		expect(fetchMock).toHaveBeenCalledTimes(2);
		const blobUrl = new URL(String(fetchMock.mock.calls[1][0]));
		expect(blobUrl.pathname).toBe("/xrpc/sh.tangled.git.temp.getBlob");
		expect(blobUrl.searchParams.get("path")).toBe("docs/README.md");
	});
});

describe("knotmirror.languages", () => {
	it("uses the languages endpoint", async () => {
		const fetchMock = vi
			.fn<typeof globalThis.fetch>()
			.mockResolvedValue(
				jsonResponse({ ref: "main", languages: [{ name: "TypeScript", size: 10 }] })
			);
		const ctx = knotmirror.createKnotMirrorClient("https://mirror.test", fetchMock);

		await knotmirror.languages(ctx, { repo: "did:plc:repo", ref: "main" });

		const url = new URL(String(fetchMock.mock.calls[0][0]));
		expect(url.pathname).toBe("/xrpc/sh.tangled.git.temp.listLanguages");
	});
});

describe("knotmirror.blob", () => {
	const entryBody = {
		name: "hello.txt",
		mode: "0100644",
		size: 5,
		lastCommit: {
			hash: "abc",
			message: "hi",
			author: { name: "dawn", email: "dawn@tangled.org", when: "2026-07-01T00:00:00Z" }
		}
	};

	const blobFetch = (content: Uint8Array, entry: unknown = entryBody) =>
		vi.fn<typeof globalThis.fetch>().mockImplementation(async (input) => {
			const url = new URL(String(input));
			return url.pathname.endsWith("getEntry")
				? jsonResponse(entry)
				: new Response(content.slice().buffer, { status: 200 });
		});

	it("creates the repo blob shape from getEntry plus raw content", async () => {
		const fetchMock = blobFetch(new TextEncoder().encode("hello"));
		const ctx = knotmirror.createKnotMirrorClient("https://mirror.test", fetchMock);

		const output = await knotmirror.blob(
			ctx,
			{ repo: "did:plc:repo", ref: "main", path: "hello.txt" },
			1024
		);

		expect(output).toMatchObject({
			path: "hello.txt",
			size: 5,
			isBinary: false,
			encoding: "utf-8",
			content: "hello",
			lastCommit: { hash: "abc", when: "2026-07-01T00:00:00Z" }
		});
	});

	it("skips the content fetch when the entry is over the inline cap", async () => {
		const fetchMock = blobFetch(new TextEncoder().encode("hello"), { ...entryBody, size: 2048 });
		const ctx = knotmirror.createKnotMirrorClient("https://mirror.test", fetchMock);

		const output = await knotmirror.blob(
			ctx,
			{ repo: "did:plc:repo", ref: "main", path: "hello.txt" },
			1024
		);

		expect(output.fileTooLarge).toBe(true);
		expect(output.content).toBeUndefined();
		expect(fetchMock).toHaveBeenCalledTimes(1);
	});

	it("marks content with null bytes as binary", async () => {
		const fetchMock = blobFetch(new Uint8Array([0x50, 0x4b, 0x00, 0x00]));
		const ctx = knotmirror.createKnotMirrorClient("https://mirror.test", fetchMock);

		const output = await knotmirror.blob(
			ctx,
			{ repo: "did:plc:repo", ref: "main", path: "hello.txt" },
			1024
		);

		expect(output.isBinary).toBe(true);
		expect(output.content).toBeUndefined();
	});

	it("treats a gitlink mode as a submodule", async () => {
		const fetchMock = blobFetch(new Uint8Array(), { ...entryBody, mode: "0160000" });
		const ctx = knotmirror.createKnotMirrorClient("https://mirror.test", fetchMock);

		const output = await knotmirror.blob(
			ctx,
			{ repo: "did:plc:repo", ref: "main", path: "hello.txt" },
			1024
		);

		expect(output.submodule).toEqual({ name: "hello.txt", url: "" });
	});
});
