import { error } from "@sveltejs/kit";
import type { RequestHandler } from "./$types";

const UPSTREAM_ORIGIN = "https://nawdist.animemusicquiz.com";
const ALLOWED_METHODS = ["GET", "HEAD"] as const;

// Basic filename hygiene: avoid path traversal and weird encodings.
// If you need to support subdirectories later, relax this and validate segments.
function isSafeFilename(name: string) {
	// Disallow slashes/backslashes and any ".."
	if (
		!name ||
		name.includes("/") ||
		name.includes("\\") ||
		name.includes("..")
	) {
		return false;
	}

	// Keep it reasonably strict; CDN filenames are typically simple.
	// Allow common audio/container extensions and typical characters.
	return /^[A-Za-z0-9][A-Za-z0-9._ -]*$/.test(name);
}

function pickForwardHeader(request: Request, headerName: string) {
	const v = request.headers.get(headerName);
	return v ?? undefined;
}

export const GET: RequestHandler = async ({
	params,
	request,
	fetch,
	setHeaders,
}) => {
	const filename = params.filename;

	if (!isSafeFilename(filename)) {
		throw error(400, "Invalid filename");
	}

	// Forward Range so audio seeking works
	const range = pickForwardHeader(request, "range");
	const ifNoneMatch = pickForwardHeader(request, "if-none-match");
	const ifModifiedSince = pickForwardHeader(request, "if-modified-since");

	const upstreamUrl = `${UPSTREAM_ORIGIN}/${encodeURIComponent(filename)}`;

	const upstreamRes = await fetch(upstreamUrl, {
		method: "GET",
		headers: {
			...(range ? { range } : {}),
			...(ifNoneMatch ? { "if-none-match": ifNoneMatch } : {}),
			...(ifModifiedSince ? { "if-modified-since": ifModifiedSince } : {}),
		},
	});

	// Map some upstream statuses sensibly
	if (upstreamRes.status === 404) throw error(404, "File not found");
	if (upstreamRes.status === 416) {
		// Range Not Satisfiable (e.g. stale range) should pass through
		return new Response(upstreamRes.body, {
			status: upstreamRes.status,
			statusText: upstreamRes.statusText,
			headers: upstreamRes.headers,
		});
	}
	if (
		!upstreamRes.ok &&
		upstreamRes.status !== 206 &&
		upstreamRes.status !== 304
	) {
		throw error(upstreamRes.status, `Upstream error (${upstreamRes.status})`);
	}

	// Pass through headers that matter for media + caching.
	// Avoid copying hop-by-hop headers.
	const passthrough = new Headers();

	const contentType = upstreamRes.headers.get("content-type");
	if (contentType) passthrough.set("content-type", contentType);

	const contentLength = upstreamRes.headers.get("content-length");
	if (contentLength) passthrough.set("content-length", contentLength);

	const acceptRanges = upstreamRes.headers.get("accept-ranges");
	if (acceptRanges) passthrough.set("accept-ranges", acceptRanges);

	const contentRange = upstreamRes.headers.get("content-range");
	if (contentRange) passthrough.set("content-range", contentRange);

	const etag = upstreamRes.headers.get("etag");
	if (etag) passthrough.set("etag", etag);

	const lastModified = upstreamRes.headers.get("last-modified");
	if (lastModified) passthrough.set("last-modified", lastModified);

	const cacheControl = upstreamRes.headers.get("cache-control");
	if (cacheControl) passthrough.set("cache-control", cacheControl);
	else passthrough.set("cache-control", "public, max-age=86400");

	// CORS: allow your app to fetch this endpoint from anywhere you deploy it
	passthrough.set("access-control-allow-origin", "*");
	passthrough.set("access-control-allow-methods", ALLOWED_METHODS.join(", "));
	passthrough.set(
		"access-control-allow-headers",
		"range, if-none-match, if-modified-since",
	);

	// Tell browsers/proxies that Range affects the response
	passthrough.set("vary", "range");

	// Also apply via SvelteKit helper so adapters that manage headers behave
	setHeaders(Object.fromEntries(passthrough.entries()));

	// 304 responses have no body
	if (upstreamRes.status === 304) {
		return new Response(null, { status: 304, headers: passthrough });
	}

	return new Response(upstreamRes.body, {
		status: upstreamRes.status,
		statusText: upstreamRes.statusText,
		headers: passthrough,
	});
};

// HEAD support for players/browsers that probe first
export const HEAD: RequestHandler = async (event) => {
	// Reuse GET logic but avoid streaming a body.
	// We still want upstream to evaluate Range/ETag, so call upstream with HEAD.
	const { params, request, fetch, setHeaders } = event;
	const filename = params.filename;

	if (!isSafeFilename(filename)) {
		throw error(400, "Invalid filename");
	}

	const range = pickForwardHeader(request, "range");
	const ifNoneMatch = pickForwardHeader(request, "if-none-match");
	const ifModifiedSince = pickForwardHeader(request, "if-modified-since");

	const upstreamUrl = `${UPSTREAM_ORIGIN}/${encodeURIComponent(filename)}`;

	const upstreamRes = await fetch(upstreamUrl, {
		method: "HEAD",
		headers: {
			...(range ? { range } : {}),
			...(ifNoneMatch ? { "if-none-match": ifNoneMatch } : {}),
			...(ifModifiedSince ? { "if-modified-since": ifModifiedSince } : {}),
		},
	});

	if (upstreamRes.status === 404) throw error(404, "File not found");
	if (
		!upstreamRes.ok &&
		upstreamRes.status !== 206 &&
		upstreamRes.status !== 304
	) {
		throw error(upstreamRes.status, `Upstream error (${upstreamRes.status})`);
	}

	const headers = new Headers();

	const copy = (h: string) => {
		const v = upstreamRes.headers.get(h);
		if (v) headers.set(h, v);
	};

	copy("content-type");
	copy("content-length");
	copy("accept-ranges");
	copy("content-range");
	copy("etag");
	copy("last-modified");
	copy("cache-control");

	if (!headers.has("cache-control")) {
		headers.set("cache-control", "public, max-age=86400");
	}

	headers.set("access-control-allow-origin", "*");
	headers.set("access-control-allow-methods", ALLOWED_METHODS.join(", "));
	headers.set(
		"access-control-allow-headers",
		"range, if-none-match, if-modified-since",
	);
	headers.set("vary", "range");

	setHeaders(Object.fromEntries(headers.entries()));

	return new Response(null, { status: upstreamRes.status, headers });
};
