/**
 * Server-side MinIO presign helper.
 * Calls the backend API to get presigned URLs, then optionally rewrites
 * the MinIO host to the public-facing URL for browser use.
 *
 * Never import this from client-side code.
 */

import { env as pubEnv } from '$env/dynamic/public';
import { log } from '$lib/server/logger';
import { backendFetch } from '$lib/server/scraper';

// Public MinIO URL — used to rewrite presigned URLs so the browser can reach MinIO directly.
// In docker-compose this would differ from the internal endpoint.
const MINIO_PUBLIC_URL = pubEnv.PUBLIC_MINIO_PUBLIC_URL ?? 'http://localhost:9000';

// ─── Avatar helpers ───────────────────────────────────────────────────────────

function extFromMime(mime: string): string {
	if (mime.includes('png')) return 'png';
	if (mime.includes('webp')) return 'webp';
	if (mime.includes('gif')) return 'gif';
	return 'jpg';
}

/**
 * Returns a short-lived presigned PUT URL for uploading an avatar directly to MinIO,
 * along with the object key to record in PocketBase after upload completes.
 * Routed through the Go backend which holds MinIO credentials.
 */
export async function presignAvatarUploadUrl(userId: string, mimeType: string): Promise<{ uploadUrl: string; key: string }> {
	const ext = extFromMime(mimeType);
	const res = await backendFetch(`/api/presign/avatar-upload/${encodeURIComponent(userId)}?ext=${ext}`);
	if (!res.ok) {
		const body = await res.text().catch(() => '');
		throw new Error(`presign avatar upload failed: ${res.status} ${body}`);
	}
	const data = (await res.json()) as { upload_url: string; key: string };
	return { uploadUrl: data.upload_url, key: data.key };
}

/**
 * Returns a presigned GET URL for a user's avatar from MinIO.
 * Returns null if no avatar object exists in MinIO for this user.
 */
export async function presignAvatarUrl(userId: string): Promise<string | null> {
	const res = await backendFetch(`/api/presign/avatar/${encodeURIComponent(userId)}`);
	if (res.status === 404) return null;
	if (!res.ok) {
		const body = await res.text().catch(() => '');
		throw new Error(`presign avatar failed: ${res.status} ${body}`);
	}
	const data = (await res.json()) as { url: string };
	return data.url ? rewriteHost(data.url) : null;
}

/**
 * Resolves the best available avatar URL for a user.
 *
 * Priority:
 *   1. MinIO — if the user has uploaded a custom avatar it will be found here
 *      (presigned, short-lived GET URL).
 *   2. OAuth provider URL — stored in avatar_url when the account was created
 *      via Google / GitHub OAuth (e.g. https://lh3.googleusercontent.com/...).
 *      Returned as-is; the browser fetches it directly.
 *
 * Pass the raw `avatar_url` field from the PocketBase record as `storedValue`
 * so this function can distinguish between a MinIO key and a remote URL without
 * an extra DB round-trip.
 *
 * Returns null when neither source yields an avatar.
 */
export async function resolveAvatarUrl(
	userId: string,
	storedValue: string | null | undefined
): Promise<string | null> {
	// 1. Try MinIO first (custom upload takes priority over OAuth picture).
	try {
		const minioUrl = await presignAvatarUrl(userId);
		if (minioUrl) return minioUrl;
	} catch {
		// MinIO unavailable — fall through to OAuth fallback.
	}

	// 2. Fall back to OAuth-provided picture URL if it looks like a remote URL.
	if (storedValue && storedValue.startsWith('http')) {
		return storedValue;
	}

	return null;
}

/**
 * Rewrites the MinIO host in a presigned URL to the public-facing URL.
 *
 * The Go backend presigns URLs against its internal endpoint (e.g. minio:9000)
 * when PUBLIC_MINIO_PUBLIC_URL is not set or equals the internal endpoint.
 * In that case the browser must reach MinIO via the public URL (e.g.
 * localhost:9000 in dev), so we swap the origin.
 *
 * NOTE: AWS Signature V4 DOES include the Host header in the canonical request
 * (via X-Amz-SignedHeaders=host).  Rewriting the host here would break the
 * signature.  This function is therefore only a no-op safety net — in
 * production the Go backend is configured with MINIO_PUBLIC_ENDPOINT equal to
 * the externally-reachable hostname, so presigned URLs already carry the right
 * host and no rewrite is needed.
 *
 * For local dev: MINIO_PUBLIC_ENDPOINT=http://localhost:9000 and the backend
 * presigns with localhost:9000 (the public client), so this rewrite is again
 * a no-op (origins already match).
 */
function rewriteHost(presignedUrl: string): string {
	try {
		const u = new URL(presignedUrl);
		const pub = new URL(MINIO_PUBLIC_URL);
		// No-op if already pointing at the right origin.
		if (u.protocol === pub.protocol && u.hostname === pub.hostname && u.port === pub.port) {
			return presignedUrl;
		}
		u.protocol = pub.protocol;
		u.hostname = pub.hostname;
		u.port = pub.port;
		return u.toString();
	} catch {
		return presignedUrl;
	}
}

/**
 * Returns a presigned URL for a chapter markdown file.
 * URL is valid for ~15 minutes (set by the scraper).
 *
 * @param rewrite - if true, rewrites the MinIO host to PUBLIC_MINIO_PUBLIC_URL
 *   (for browser use). Defaults to false — the server-side load function fetches
 *   the URL directly from the internal MinIO endpoint.
 */
export async function presignChapter(slug: string, n: number, rewrite = false): Promise<string> {
	log.debug('minio', 'presigning chapter', { slug, n });
	let res: Response;
	try {
		res = await backendFetch(`/api/presign/chapter/${slug}/${n}`);
	} catch (e) {
		log.error('minio', 'presign chapter network error', { slug, n, err: String(e) });
		throw new Error(`presign chapter ${slug}/${n}: network error`);
	}
	if (!res.ok) {
		const body = await res.text().catch(() => '');
		log.error('minio', 'presign chapter failed', { slug, n, status: res.status, body });
		throw new Error(`presign chapter ${slug}/${n}: ${res.status}`);
	}
	const data = (await res.json()) as { url: string };
	log.debug('minio', 'presign chapter ok', { slug, n });
	return rewrite ? rewriteHost(data.url) : data.url;
}

/**
 * Returns a presigned URL for a voice sample audio file.
 * URL is valid for ~1 hour. The URL is returned to the browser for direct streaming.
 * Throws with { status: 404 } when the sample has not been generated yet.
 */
export async function presignVoiceSample(voice: string): Promise<string> {
	log.debug('minio', 'presigning voice sample', { voice });
	let res: Response;
	try {
		res = await backendFetch(`/api/presign/voice-sample/${encodeURIComponent(voice)}`);
	} catch (e) {
		log.error('minio', 'presign voice sample network error', { voice, err: String(e) });
		throw new Error(`presign voice sample ${voice}: network error`);
	}
	if (res.status === 404) {
		const err = new Error(`presign voice sample ${voice}: not found`) as Error & { status: number };
		err.status = 404;
		throw err;
	}
	if (!res.ok) {
		const body = await res.text().catch(() => '');
		log.error('minio', 'presign voice sample failed', { voice, status: res.status, body });
		throw new Error(`presign voice sample ${voice}: ${res.status}`);
	}
	const data = (await res.json()) as { url: string };
	log.debug('minio', 'presign voice sample ok', { voice });
	return rewriteHost(data.url);
}

/**
 * Returns a presigned URL for an audio file.
 * URL is valid for ~1 hour. The URL is returned to the browser for direct streaming.
 * Throws with { status: 404 } when the audio object has not been generated yet.
 */
export async function presignAudio(
	slug: string,
	n: number,
	voice?: string
): Promise<string> {
	const params = new URLSearchParams();
	if (voice) params.set('voice', voice);
	const qs = params.toString() ? `?${params.toString()}` : '';
	log.debug('minio', 'presigning audio', { slug, n, voice });
	let res: Response;
	try {
		res = await backendFetch(`/api/presign/audio/${slug}/${n}${qs}`);
	} catch (e) {
		log.error('minio', 'presign audio network error', { slug, n, err: String(e) });
		throw new Error(`presign audio ${slug}/${n}: network error`);
	}
	if (res.status === 404) {
		// Audio hasn't been generated / uploaded yet — caller should surface this as 404.
		const err = new Error(`presign audio ${slug}/${n}: not found`) as Error & { status: number };
		err.status = 404;
		throw err;
	}
	if (!res.ok) {
		const body = await res.text().catch(() => '');
		log.error('minio', 'presign audio failed', { slug, n, status: res.status, body });
		throw new Error(`presign audio ${slug}/${n}: ${res.status}`);
	}
	const data = (await res.json()) as { url: string };
	log.debug('minio', 'presign audio ok', { slug, n });
	return rewriteHost(data.url);
}
