/**
 * Backup Store — Server-side opaque blob storage for encrypted backups.
 *
 * Layout: /data/backups/{userId}/{spaceSlug}/{docId-hash}.enc
 * Manifest: /data/backups/{userId}/{spaceSlug}/manifest.json
 *
 * The server stores ciphertext blobs it cannot decrypt (zero-knowledge).
 * Clients encrypt before upload and decrypt after download.
 */

import { resolve, dirname } from "node:path";
import { mkdir, readdir, readFile, writeFile, unlink, stat, rm } from "node:fs/promises";
import { createHash } from "node:crypto";
import { isIPFSEnabled, pinToIPFS, unpinFromIPFS } from "../ipfs";

const BACKUPS_DIR = process.env.BACKUPS_DIR || "/data/backups";

export interface BackupManifestEntry {
	docId: string;
	hash: string;
	size: number;
	updatedAt: string;
	cid?: string;
}

export interface BackupManifest {
	spaceSlug: string;
	entries: BackupManifestEntry[];
	updatedAt: string;
}

/** Hash a docId into a safe filename. */
function docIdHash(docId: string): string {
	return createHash("sha256").update(docId).digest("hex").slice(0, 32);
}

/** Resolve the directory for a user+space backup. */
function backupDir(userId: string, spaceSlug: string): string {
	return resolve(BACKUPS_DIR, userId, spaceSlug);
}

/** Resolve the path for a specific blob. */
function blobPath(userId: string, spaceSlug: string, docId: string): string {
	return resolve(backupDir(userId, spaceSlug), `${docIdHash(docId)}.enc`);
}

/** Resolve the manifest path. */
function manifestPath(userId: string, spaceSlug: string): string {
	return resolve(backupDir(userId, spaceSlug), "manifest.json");
}

/** Load a manifest (returns empty manifest if none exists). */
async function loadManifest(
	userId: string,
	spaceSlug: string,
): Promise<BackupManifest> {
	try {
		const path = manifestPath(userId, spaceSlug);
		const file = Bun.file(path);
		if (await file.exists()) {
			return (await file.json()) as BackupManifest;
		}
	} catch {
		// Corrupt or missing manifest
	}
	return { spaceSlug, entries: [], updatedAt: new Date().toISOString() };
}

/** Save a manifest. */
async function saveManifest(
	userId: string,
	spaceSlug: string,
	manifest: BackupManifest,
): Promise<void> {
	const path = manifestPath(userId, spaceSlug);
	await mkdir(dirname(path), { recursive: true });
	await writeFile(path, JSON.stringify(manifest, null, 2));
}

/**
 * Store an encrypted backup blob.
 */
export async function putBackup(
	userId: string,
	spaceSlug: string,
	docId: string,
	blob: Uint8Array,
): Promise<void> {
	const path = blobPath(userId, spaceSlug, docId);
	await mkdir(dirname(path), { recursive: true });
	await writeFile(path, blob);

	// Update manifest
	const manifest = await loadManifest(userId, spaceSlug);
	const hash = createHash("sha256").update(blob).digest("hex");
	const existing = manifest.entries.findIndex((e) => e.docId === docId);
	const entry: BackupManifestEntry = {
		docId,
		hash,
		size: blob.byteLength,
		updatedAt: new Date().toISOString(),
	};

	if (existing >= 0) {
		manifest.entries[existing] = entry;
	} else {
		manifest.entries.push(entry);
	}
	manifest.updatedAt = new Date().toISOString();
	await saveManifest(userId, spaceSlug, manifest);

	// Fire-and-forget IPFS pin — filesystem is primary, IPFS is redundancy
	if (isIPFSEnabled()) {
		pinToIPFS(blob, `${docIdHash(docId)}.enc`)
			.then((cid) => {
				// Update manifest entry with CID
				const idx = manifest.entries.findIndex((e) => e.docId === docId);
				if (idx >= 0) {
					manifest.entries[idx].cid = cid;
					saveManifest(userId, spaceSlug, manifest).catch(() => {});
				}
			})
			.catch((err) => {
				console.warn("[backup] IPFS pin failed (non-fatal):", err.message);
			});
	}
}

/**
 * Retrieve an encrypted backup blob.
 */
export async function getBackup(
	userId: string,
	spaceSlug: string,
	docId: string,
): Promise<Uint8Array | null> {
	try {
		const path = blobPath(userId, spaceSlug, docId);
		const file = Bun.file(path);
		if (await file.exists()) {
			const buffer = await file.arrayBuffer();
			return new Uint8Array(buffer);
		}
	} catch {
		// File doesn't exist
	}
	return null;
}

/**
 * List all backup entries for a space.
 */
export async function listBackups(
	userId: string,
	spaceSlug: string,
): Promise<BackupManifest> {
	return loadManifest(userId, spaceSlug);
}

/**
 * Delete a specific backup blob.
 */
export async function deleteBackup(
	userId: string,
	spaceSlug: string,
	docId: string,
): Promise<boolean> {
	try {
		// Check for IPFS CID before removing from manifest
		const manifest = await loadManifest(userId, spaceSlug);
		const entry = manifest.entries.find((e) => e.docId === docId);

		const path = blobPath(userId, spaceSlug, docId);
		await unlink(path);

		// Fire-and-forget IPFS unpin
		if (entry?.cid && isIPFSEnabled()) {
			unpinFromIPFS(entry.cid).catch((err) => {
				console.warn("[backup] IPFS unpin failed (non-fatal):", err.message);
			});
		}

		// Update manifest
		manifest.entries = manifest.entries.filter((e) => e.docId !== docId);
		manifest.updatedAt = new Date().toISOString();
		await saveManifest(userId, spaceSlug, manifest);
		return true;
	} catch {
		return false;
	}
}

/**
 * Delete all backups for a space.
 */
export async function deleteAllBackups(
	userId: string,
	spaceSlug: string,
): Promise<boolean> {
	try {
		const dir = backupDir(userId, spaceSlug);
		await rm(dir, { recursive: true, force: true });
		return true;
	} catch {
		return false;
	}
}

/**
 * Get total backup storage usage for a user.
 */
export async function getUsage(userId: string): Promise<{
	totalBytes: number;
	spaceCount: number;
	docCount: number;
}> {
	let totalBytes = 0;
	let spaceCount = 0;
	let docCount = 0;

	try {
		const userDir = resolve(BACKUPS_DIR, userId);
		const spaces = await readdir(userDir, { withFileTypes: true });
		for (const entry of spaces) {
			if (!entry.isDirectory()) continue;
			spaceCount++;
			const manifest = await loadManifest(userId, entry.name);
			for (const e of manifest.entries) {
				totalBytes += e.size;
				docCount++;
			}
		}
	} catch {
		// User has no backups
	}

	return { totalBytes, spaceCount, docCount };
}
