import { randomBytes } from "node:crypto";
import {
	closeSync,
	existsSync,
	fsyncSync,
	mkdirSync,
	openSync,
	readdirSync,
	readFileSync,
	renameSync,
	unlinkSync,
	writeFileSync,
} from "node:fs";
import { basename, dirname } from "node:path";

/**
 * Clean up orphan .tmp.* files for the given target path.
 * Scans the directory for stale temporary files matching the basename pattern.
 */
function cleanOrphanTmpFiles(targetPath) {
	try {
		const dir = dirname(targetPath);
		const name = basename(targetPath);
		const files = readdirSync(dir);
		for (const file of files) {
			if (file.startsWith(name) && file.includes(".tmp.")) {
				try {
					unlinkSync(`${dir}/${file}`);
				} catch {
					// Best-effort cleanup only
				}
			}
		}
	} catch {
		// Best-effort cleanup only
	}
}
/**
 * Load a JSON file with validation, returning a default on failure.
 * Handles missing files, corrupt JSON, and schema mismatches uniformly.
 */
export function loadJsonFile(filePath, validate, defaultFactory) {
	try {
		if (!existsSync(filePath)) return defaultFactory();
		const raw = readFileSync(filePath, "utf-8");
		const parsed = JSON.parse(raw);
		return validate(parsed) ? parsed : defaultFactory();
	} catch {
		return defaultFactory();
	}
}
/**
 * Load a JSON file with validation, returning null on failure.
 * For callers that distinguish "no data" from "default data".
 */
export function loadJsonFileOrNull(filePath, validate) {
	try {
		if (!existsSync(filePath)) return null;
		const raw = readFileSync(filePath, "utf-8");
		const parsed = JSON.parse(raw);
		return validate(parsed) ? parsed : null;
	} catch {
		return null;
	}
}
/**
 * Save a JSON file atomically (write to .tmp, then rename).
 * Creates parent directories as needed.
 * Non-fatal — swallows errors to prevent persistence from breaking operations.
 *
 * Uses atomic write-tmp-rename to prevent partial/corrupt files on crash.
 * This is the canonical way to persist JSON state in SF — all callers
 * (queue-order, metrics, routing-history, reactive-graph) benefit from
 * crash-safety without code changes.
 */
export function saveJsonFile(filePath, data) {
	try {
		const dir = dirname(filePath);
		mkdirSync(dir, { recursive: true });
		// Remove orphaned .tmp.* files from prior crashed writes before creating
		// a new one. On Windows a locked stale tmp file causes renameSync to fail.
		cleanOrphanTmpFiles(filePath);
		// Use randomized tmp suffix to prevent concurrent-write data loss
		const tmp = `${filePath}.tmp.${randomBytes(4).toString("hex")}`;
		writeFileSync(tmp, JSON.stringify(data, null, 2) + "\n", "utf-8");
		// fsync the tmp file so its data is durable before the rename is visible
		const tmpFd = openSync(tmp, "r");
		try {
			fsyncSync(tmpFd);
		} finally {
			closeSync(tmpFd);
		}
		renameSync(tmp, filePath);
		// fsync the target file so its data is durable after the rename
		const targetFd = openSync(filePath, "r");
		try {
			fsyncSync(targetFd);
		} finally {
			closeSync(targetFd);
		}
		// fsync the directory so the rename (directory entry update) is durable
		const dirFd = openSync(dir, "r");
		try {
			fsyncSync(dirFd);
		} finally {
			closeSync(dirFd);
		}
		// No cleanup needed — renameSync atomically removes tmp on success
	} catch {
		// Non-fatal — don't let persistence failures break operation
	}
}
/**
 * Write a JSON file atomically (write to .tmp, then rename).
 * Creates parent directories as needed. Non-fatal on error.
 */
export function writeJsonFileAtomic(filePath, data) {
	try {
		const dir = dirname(filePath);
		mkdirSync(dir, { recursive: true });
		cleanOrphanTmpFiles(filePath);
		const tmp = `${filePath}.tmp.${randomBytes(4).toString("hex")}`;
		writeFileSync(tmp, JSON.stringify(data, null, 2), "utf-8");
		// fsync the tmp file so its data is durable before the rename is visible
		const tmpFd = openSync(tmp, "r");
		try {
			fsyncSync(tmpFd);
		} finally {
			closeSync(tmpFd);
		}
		renameSync(tmp, filePath);
		// fsync the target file so its data is durable after the rename
		const targetFd = openSync(filePath, "r");
		try {
			fsyncSync(targetFd);
		} finally {
			closeSync(targetFd);
		}
		// fsync the directory so the rename (directory entry update) is durable
		const dirFd = openSync(dir, "r");
		try {
			fsyncSync(dirFd);
		} finally {
			closeSync(dirFd);
		}
	} catch {
		// Non-fatal — don't let persistence failures break operation
	}
}
