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

import type { Interceptor } from "../../../src/client/transport.js";
import {
	createTransport,
	csrfInterceptor,
	tokenInterceptor,
} from "../../../src/client/transport.js";

// ---------------------------------------------------------------------------
// Helpers
// ---------------------------------------------------------------------------

/** Create an interceptor that adds a header to the request */
function createHeaderInterceptor(name: string, value: string): Interceptor {
	return async (req, next) => {
		const headers = new Headers(req.headers);
		headers.set(name, value);
		return next(new Request(req, { headers }));
	};
}

/** Create a mock fetch that returns a fixed response */
function mockFetch(body: unknown = {}, status: number = 200): Interceptor {
	return async () =>
		new Response(JSON.stringify(body), {
			status,
			headers: { "Content-Type": "application/json" },
		});
}

// ---------------------------------------------------------------------------
// createTransport
// ---------------------------------------------------------------------------

describe("createTransport", () => {
	it("calls global fetch when no interceptors are provided", async () => {
		const transport = createTransport({
			interceptors: [mockFetch({ ok: true })],
		});

		const res = await transport.fetch(new Request("https://example.com"));
		expect(res.status).toBe(200);
		const json = await res.json();
		expect(json).toEqual({ ok: true });
	});

	it("runs interceptors in order", async () => {
		const order: string[] = [];

		const first: Interceptor = async (req, next) => {
			order.push("first-before");
			const res = await next(req);
			order.push("first-after");
			return res;
		};

		const second: Interceptor = async (req, next) => {
			order.push("second-before");
			const res = await next(req);
			order.push("second-after");
			return res;
		};

		const transport = createTransport({
			interceptors: [first, second, mockFetch()],
		});

		await transport.fetch(new Request("https://example.com"));
		expect(order).toEqual(["first-before", "second-before", "second-after", "first-after"]);
	});

	it("allows interceptors to modify requests", async () => {
		let capturedHeader: string | null = null;

		const addHeader = createHeaderInterceptor("X-Custom", "test-value");

		const capture: Interceptor = async (req) => {
			capturedHeader = req.headers.get("X-Custom");
			return new Response("ok");
		};

		const transport = createTransport({
			interceptors: [addHeader, capture],
		});

		await transport.fetch(new Request("https://example.com"));
		expect(capturedHeader).toBe("test-value");
	});

	it("allows interceptors to retry on failure", async () => {
		let attempts = 0;

		const retryOnce: Interceptor = async (req, next) => {
			const res = await next(req);
			if (res.status === 401 && attempts === 0) {
				attempts++;
				return next(req);
			}
			return res;
		};

		let callCount = 0;
		const backend: Interceptor = async () => {
			callCount++;
			if (callCount === 1) {
				return new Response("unauthorized", { status: 401 });
			}
			return new Response("ok", { status: 200 });
		};

		const transport = createTransport({
			interceptors: [retryOnce, backend],
		});

		const res = await transport.fetch(new Request("https://example.com"));
		expect(res.status).toBe(200);
		expect(callCount).toBe(2);
	});
});

// ---------------------------------------------------------------------------
// csrfInterceptor
// ---------------------------------------------------------------------------

describe("csrfInterceptor", () => {
	it("adds X-EmDash-Request header to POST requests", async () => {
		let capturedHeader: string | null = null;
		const capture: Interceptor = async (req) => {
			capturedHeader = req.headers.get("X-EmDash-Request");
			return new Response("ok");
		};

		const transport = createTransport({
			interceptors: [csrfInterceptor(), capture],
		});

		await transport.fetch(new Request("https://example.com", { method: "POST" }));
		expect(capturedHeader).toBe("1");
	});

	it("adds X-EmDash-Request header to PUT requests", async () => {
		let capturedHeader: string | null = null;
		const capture: Interceptor = async (req) => {
			capturedHeader = req.headers.get("X-EmDash-Request");
			return new Response("ok");
		};

		const transport = createTransport({
			interceptors: [csrfInterceptor(), capture],
		});

		await transport.fetch(new Request("https://example.com", { method: "PUT" }));
		expect(capturedHeader).toBe("1");
	});

	it("adds X-EmDash-Request header to DELETE requests", async () => {
		let capturedHeader: string | null = null;
		const capture: Interceptor = async (req) => {
			capturedHeader = req.headers.get("X-EmDash-Request");
			return new Response("ok");
		};

		const transport = createTransport({
			interceptors: [csrfInterceptor(), capture],
		});

		await transport.fetch(new Request("https://example.com", { method: "DELETE" }));
		expect(capturedHeader).toBe("1");
	});

	it("does NOT add header to GET requests", async () => {
		let capturedHeader: string | null = null;
		const capture: Interceptor = async (req) => {
			capturedHeader = req.headers.get("X-EmDash-Request");
			return new Response("ok");
		};

		const transport = createTransport({
			interceptors: [csrfInterceptor(), capture],
		});

		await transport.fetch(new Request("https://example.com", { method: "GET" }));
		expect(capturedHeader).toBeNull();
	});
});

// ---------------------------------------------------------------------------
// tokenInterceptor
// ---------------------------------------------------------------------------

describe("tokenInterceptor", () => {
	it("adds Authorization Bearer header to all requests", async () => {
		let capturedAuth: string | null = null;
		const capture: Interceptor = async (req) => {
			capturedAuth = req.headers.get("Authorization");
			return new Response("ok");
		};

		const transport = createTransport({
			interceptors: [tokenInterceptor("ec_pat_abc123"), capture],
		});

		await transport.fetch(new Request("https://example.com"));
		expect(capturedAuth).toBe("Bearer ec_pat_abc123");
	});

	it("adds Authorization to both GET and POST", async () => {
		const captured: string[] = [];
		const capture: Interceptor = async (req) => {
			captured.push(req.headers.get("Authorization") ?? "");
			return new Response("ok");
		};

		const transport = createTransport({
			interceptors: [tokenInterceptor("tok"), capture],
		});

		await transport.fetch(new Request("https://example.com", { method: "GET" }));
		await transport.fetch(new Request("https://example.com", { method: "POST" }));
		expect(captured).toEqual(["Bearer tok", "Bearer tok"]);
	});
});

// ---------------------------------------------------------------------------
// Interceptor composition
// ---------------------------------------------------------------------------

describe("interceptor composition", () => {
	it("csrf + token interceptors compose correctly", async () => {
		let capturedAuth: string | null = null;
		let capturedCsrf: string | null = null;

		const capture: Interceptor = async (req) => {
			capturedAuth = req.headers.get("Authorization");
			capturedCsrf = req.headers.get("X-EmDash-Request");
			return new Response("ok");
		};

		const transport = createTransport({
			interceptors: [csrfInterceptor(), tokenInterceptor("tok"), capture],
		});

		await transport.fetch(new Request("https://example.com", { method: "POST" }));
		expect(capturedAuth).toBe("Bearer tok");
		expect(capturedCsrf).toBe("1");
	});
});
