import { createHash } from 'node:crypto';
import { existsSync } from 'node:fs';
import { copyFile, mkdir, readdir, unlink, writeFile } from 'node:fs/promises';
import path from 'node:path';

const PUBLIC_DIR = path.resolve(process.cwd(), 'public', 'images', 'webmention');
const DIST_DIR = path.resolve(process.cwd(), 'dist', 'images', 'webmention');
const URL_PATH = '/images/webmention';

const EXT_BY_MIME: Record<string, string> = {
	'image/jpeg': 'jpg',
	'image/jpg': 'jpg',
	'image/png': 'png',
	'image/webp': 'webp',
	'image/gif': 'gif',
	'image/avif': 'avif',
	'image/svg+xml': 'svg',
};

const memo = new Map<string, Promise<string | null>>();
const usedFilenames = new Set<string>();

function extFromUrl(url: string): string | null {
	const m = url.match(/\.(jpe?g|png|webp|gif|avif|svg)(?:\?|#|$)/i);
	return m ? m[1].toLowerCase().replace('jpeg', 'jpg') : null;
}

async function mirrorToDist(filename: string, srcPath: string) {
	try {
		await mkdir(DIST_DIR, { recursive: true });
		const dest = path.join(DIST_DIR, filename);
		if (!existsSync(dest)) await copyFile(srcPath, dest);
	} catch {
		// dist/ may not exist in `astro dev` — ignore
	}
}

async function download(url: string): Promise<string | null> {
	const hash = createHash('sha1').update(url).digest('hex').slice(0, 16);
	const urlExt = extFromUrl(url);

	await mkdir(PUBLIC_DIR, { recursive: true });

	if (urlExt) {
		const filename = `${hash}.${urlExt}`;
		const cached = path.join(PUBLIC_DIR, filename);
		if (existsSync(cached)) {
			usedFilenames.add(filename);
			await mirrorToDist(filename, cached);
			return `${URL_PATH}/${filename}`;
		}
	}

	try {
		const res = await fetch(url, { signal: AbortSignal.timeout(10_000) });
		if (!res.ok) return null;
		const mime = (res.headers.get('content-type') ?? '').split(';')[0].trim().toLowerCase();
		const ext = EXT_BY_MIME[mime] ?? urlExt ?? 'jpg';
		const filename = `${hash}.${ext}`;
		const filepath = path.join(PUBLIC_DIR, filename);
		if (!existsSync(filepath)) {
			const buf = Buffer.from(await res.arrayBuffer());
			await writeFile(filepath, buf);
		}
		usedFilenames.add(filename);
		await mirrorToDist(filename, filepath);
		return `${URL_PATH}/${filename}`;
	} catch {
		return null;
	}
}

export function cacheAvatar(url: string | undefined): Promise<string | null> {
	if (!url) return Promise.resolve(null);
	if (!/^https?:\/\//i.test(url)) return Promise.resolve(url);
	let pending = memo.get(url);
	if (!pending) {
		pending = download(url);
		memo.set(url, pending);
	}
	return pending;
}

async function sweepDir(dir: string): Promise<number> {
	if (!existsSync(dir)) return 0;
	const entries = await readdir(dir);
	let removed = 0;
	await Promise.all(
		entries.map(async (name) => {
			if (!usedFilenames.has(name)) {
				await unlink(path.join(dir, name));
				removed++;
			}
		}),
	);
	return removed;
}

export async function sweepCache(): Promise<{ removed: number }> {
	const publicRemoved = await sweepDir(PUBLIC_DIR);
	await sweepDir(DIST_DIR);
	return { removed: publicRemoved };
}
