import { randomUUID } from "node:crypto";
import sharp from "sharp";
import { uploadImage } from "./storage.service";

const ALLOWED_TYPES = ["image/jpeg", "image/png", "image/webp"];
const MAX_SIZE = 5 * 1024 * 1024; // 5MB
const FETCH_TIMEOUT = 10_000; // 10 seconds

interface FetchImageResult {
	filename: string;
	sourceUrl: string;
	dominantColor: string | null;
}

export async function fetchImageFromUrl(
	url: string,
): Promise<FetchImageResult> {
	// Validate URL format
	let parsedUrl: URL;
	try {
		parsedUrl = new URL(url);
	} catch {
		throw new Error("Invalid URL format");
	}

	if (!["http:", "https:"].includes(parsedUrl.protocol)) {
		throw new Error("URL must use HTTP or HTTPS");
	}

	// Fetch with timeout
	const controller = new AbortController();
	const timeout = setTimeout(() => controller.abort(), FETCH_TIMEOUT);

	let response: Response;
	try {
		response = await fetch(url, { signal: controller.signal });
	} catch (err) {
		if (err instanceof DOMException && err.name === "AbortError") {
			throw new Error("Request timed out");
		}
		throw new Error(`Failed to fetch image: ${(err as Error).message}`);
	} finally {
		clearTimeout(timeout);
	}

	if (!response.ok) {
		throw new Error(`HTTP ${response.status}: Failed to fetch image`);
	}

	// Validate content type
	const contentType = response.headers
		.get("content-type")
		?.split(";")[0]
		.trim();
	if (!contentType || !ALLOWED_TYPES.includes(contentType)) {
		throw new Error(
			`Invalid content type: ${contentType ?? "unknown"}. Accepted: jpeg, png, webp`,
		);
	}

	// Check content length if available
	const contentLength = response.headers.get("content-length");
	if (contentLength && Number.parseInt(contentLength, 10) > MAX_SIZE) {
		throw new Error("File too large. Maximum size is 5MB");
	}

	// Read body and check actual size
	const buffer = await response.arrayBuffer();
	if (buffer.byteLength > MAX_SIZE) {
		throw new Error("File too large. Maximum size is 5MB");
	}

	// Determine extension
	const ext = contentType === "image/jpeg" ? "jpg" : contentType.split("/")[1];
	const filename = `${Date.now()}-${randomUUID()}.${ext}`;

	// Upload to object storage
	await uploadImage(Buffer.from(buffer), filename, contentType);

	const dominantColor = await extractDominantColor(buffer);

	return { filename, sourceUrl: url, dominantColor };
}

/**
 * Extract the dominant color from an image buffer.
 * Resizes to 1x1 pixel for a perceptually weighted average.
 * Returns hex string like '#a3b2c1' or null on failure.
 */
export async function extractDominantColor(
	buffer: Buffer | ArrayBuffer,
): Promise<string | null> {
	try {
		const { data } = await sharp(Buffer.from(buffer))
			.resize(1, 1)
			.raw()
			.toBuffer({ resolveWithObject: true });
		const r = data[0];
		const g = data[1];
		const b = data[2];
		return `#${r.toString(16).padStart(2, "0")}${g.toString(16).padStart(2, "0")}${b.toString(16).padStart(2, "0")}`;
	} catch {
		return null;
	}
}
