import type { RequestHandler } from './$types';
import { env } from 'process';

const NO_BODY_METHODS = new Set(['GET', 'HEAD', 'DELETE']);

// Hop-by-hop headers must not be forwarded to the client — they are connection-scoped.
const HOP_BY_HOP_HEADERS = new Set([
	'transfer-encoding',
	'connection',
	'keep-alive',
	'upgrade',
	'proxy-authenticate',
	'proxy-authorization',
	'te',
	'trailer',
	'proxy-connection'
]);

async function proxy(event: Parameters<RequestHandler>[0]): Promise<Response> {
	const rawPath = event.params.path;

	// Block path traversal attempts — SvelteKit decodes %2F before routing,
	// so encoded dots could produce ../../../ sequences after normalization.
	if (rawPath.includes('..') || /%(2e)/i.test(rawPath)) {
		return new Response('Bad Request', { status: 400 });
	}

	// Block Actuator endpoints — heapdump, env, configprops expose credentials
	// and heap memory and must never be reachable through the SvelteKit proxy.
	if (rawPath.startsWith('actuator')) {
		return new Response('Not Found', { status: 404 });
	}

	const apiUrl = env.API_INTERNAL_URL || 'http://localhost:8080';
	const backendUrl = `${apiUrl}/api/${rawPath}${event.url.search}`;

	const hasBody = !NO_BODY_METHODS.has(event.request.method);

	// Early exit: reject well-behaved clients that advertise an oversized body
	// before we buffer anything.
	const contentLength = event.request.headers.get('Content-Length');
	if (contentLength && parseInt(contentLength, 10) > 1_048_576) {
		return new Response('Payload Too Large', { status: 413 });
	}

	// Buffer the body so we can check its actual size. This also catches chunked
	// requests that omit Content-Length entirely (parseInt(null) → NaN → passes
	// the header check above).
	const bodyBuffer = hasBody ? await event.request.arrayBuffer() : undefined;
	if (bodyBuffer && bodyBuffer.byteLength > 1_048_576) {
		return new Response('Payload Too Large', { status: 413 });
	}

	// Only forward Content-Type from the browser request — other headers (Cookie,
	// Authorization, Accept-Encoding, etc.) must not be forwarded directly.
	// Authentication is injected by handleFetch in hooks.server.ts from the
	// server-side auth_token cookie.
	const contentType = event.request.headers.get('Content-Type');
	const requestHeaders: Record<string, string> = {};
	if (contentType) requestHeaders['Content-Type'] = contentType;

	const response = await event.fetch(backendUrl, {
		method: event.request.method,
		headers: requestHeaders,
		body: bodyBuffer
	});

	// Forward all response headers except hop-by-hop so that Content-Disposition,
	// ETag, Cache-Control, Location (redirects), etc. reach the browser.
	const responseHeaders: Record<string, string> = {};
	response.headers.forEach((value, key) => {
		if (!HOP_BY_HOP_HEADERS.has(key.toLowerCase())) {
			responseHeaders[key] = value;
		}
	});

	return new Response(response.body, {
		status: response.status,
		headers: responseHeaders
	});
}

export const GET: RequestHandler = (event) => proxy(event);
export const POST: RequestHandler = (event) => proxy(event);
export const PUT: RequestHandler = (event) => proxy(event);
export const PATCH: RequestHandler = (event) => proxy(event);
export const DELETE: RequestHandler = (event) => proxy(event);
