/**
 * Shared request handlers reused across plan, review, and annotate servers.
 * handleImageRequest, handleUploadRequest, handleDraftRequest, handleFavicon
 */

import { randomUUID } from "node:crypto";
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
import type { IncomingMessage } from "node:http";
import { tmpdir } from "node:os";
import { join, resolve as resolvePath } from "node:path";
import { saveDraft, loadDraft, deleteDraft } from "../generated/draft.js";
import { FAVICON_SVG } from "../generated/favicon.js";

import { json, parseBody, send, toWebRequest } from "./helpers";

type Res = import("node:http").ServerResponse;

const ALLOWED_IMAGE_EXTENSIONS = new Set([
	"png",
	"jpg",
	"jpeg",
	"gif",
	"webp",
	"svg",
	"bmp",
	"ico",
	"tiff",
	"tif",
	"avif",
]);

const IMAGE_CONTENT_TYPES: Record<string, string> = {
	png: "image/png",
	jpg: "image/jpeg",
	jpeg: "image/jpeg",
	gif: "image/gif",
	webp: "image/webp",
	svg: "image/svg+xml",
	bmp: "image/bmp",
	ico: "image/x-icon",
	tiff: "image/tiff",
	tif: "image/tiff",
	avif: "image/avif",
};

const UPLOAD_DIR = join(tmpdir(), "plannotator");

function getExtension(filePath: string): string {
	const lastDot = filePath.lastIndexOf(".");
	if (lastDot === -1) return "";
	return filePath.slice(lastDot + 1).toLowerCase();
}

function validateImagePath(rawPath: string): {
	valid: boolean;
	resolved: string;
	error?: string;
} {
	const resolved = resolvePath(rawPath);
	const ext = getExtension(resolved);

	if (!ALLOWED_IMAGE_EXTENSIONS.has(ext)) {
		return {
			valid: false,
			resolved,
			error: "Path does not point to a supported image file",
		};
	}

	return { valid: true, resolved };
}

function validateUploadExtension(fileName: string): {
	valid: boolean;
	ext: string;
	error?: string;
} {
	const ext = getExtension(fileName) || "png";
	if (!ALLOWED_IMAGE_EXTENSIONS.has(ext)) {
		return {
			valid: false,
			ext,
			error: `File extension ".${ext}" is not a supported image type`,
		};
	}

	return { valid: true, ext };
}

function getImageContentType(filePath: string): string {
	return (
		IMAGE_CONTENT_TYPES[getExtension(filePath)] || "application/octet-stream"
	);
}

export function handleImageRequest(res: Res, url: URL): void {
	const imagePath = url.searchParams.get("path");
	if (!imagePath) {
		send(res, "Missing path parameter", 400, { "Content-Type": "text/plain" });
		return;
	}

	const tryServePath = (candidate: string): boolean => {
		const validation = validateImagePath(candidate);
		if (!validation.valid) return false;
		try {
			if (!existsSync(validation.resolved)) return false;
			const data = readFileSync(validation.resolved);
			send(res, data, 200, {
				"Content-Type": getImageContentType(validation.resolved),
			});
			return true;
		} catch {
			return false;
		}
	};

	if (tryServePath(imagePath)) return;

	const base = url.searchParams.get("base");
	if (
		base &&
		!imagePath.startsWith("/") &&
		tryServePath(resolvePath(base, imagePath))
	) {
		return;
	}

	const validation = validateImagePath(imagePath);
	if (!validation.valid) {
		send(res, validation.error || "Invalid image path", 403, {
			"Content-Type": "text/plain",
		});
		return;
	}

	send(res, "File not found", 404, { "Content-Type": "text/plain" });
}

export async function handleUploadRequest(
	req: IncomingMessage,
	res: Res,
): Promise<void> {
	try {
		const request = toWebRequest(req);
		const formData = await request.formData();
		const file = formData.get("file");
		if (
			!file ||
			typeof file !== "object" ||
			!("arrayBuffer" in file) ||
			!("name" in file)
		) {
			json(res, { error: "No file provided" }, 400);
			return;
		}

		const upload = file as File;
		const extResult = validateUploadExtension(upload.name);
		if (!extResult.valid) {
			json(res, { error: extResult.error }, 400);
			return;
		}

		mkdirSync(UPLOAD_DIR, { recursive: true });
		const tempPath = join(UPLOAD_DIR, `${randomUUID()}.${extResult.ext}`);
		const bytes = Buffer.from(await upload.arrayBuffer());
		writeFileSync(tempPath, bytes);
		json(res, { path: tempPath, originalName: upload.name });
	} catch (err) {
		const message = err instanceof Error ? err.message : "Upload failed";
		json(res, { error: message }, 500);
	}
}

export function handleDraftRequest(
	req: IncomingMessage,
	res: Res,
	draftKey: string,
): Promise<void> | void {
	if (req.method === "POST") {
		return parseBody(req)
			.then((body) => {
				saveDraft(draftKey, body);
				json(res, { ok: true });
			})
			.catch((err: unknown) => {
				const message = err instanceof Error ? err.message : "Failed to save draft";
				console.error(`[draft] save failed: ${message}`);
				json(res, { error: message }, 500);
			});
	} else if (req.method === "DELETE") {
		deleteDraft(draftKey);
		json(res, { ok: true });
	} else {
		const draft = loadDraft(draftKey);
		if (!draft) {
			json(res, { found: false }, 404);
			return;
		}
		json(res, draft);
	}
}

export function handleFavicon(res: Res): void {
	send(res, FAVICON_SVG, 200, {
		"Content-Type": "image/svg+xml",
		"Cache-Control": "public, max-age=86400",
	});
}
