import { execFile as execFileCb } from "node:child_process";
import * as fs from "node:fs/promises";
import * as os from "node:os";
import { promisify } from "node:util";
import { OPERATION_TIMEOUT } from "../../../core/constants";
import { toMessage } from "../../../utils/errors";
import { logger } from "../../../utils/logger";
import { getMountForPath } from "../../../utils/mountinfo";
import { withTimeout } from "../../../utils/timeout";
import type { VolumeBackend } from "../backend";
import { executeMount, executeUnmount } from "../utils/backend-utils";
import { BACKEND_STATUS, type BackendConfig } from "~/schemas/volumes";

const execFile = promisify(execFileCb);

const mount = async (config: BackendConfig, path: string) => {
	logger.debug(`Mounting WebDAV volume ${path}...`);

	if (config.backend !== "webdav") {
		logger.error("Provided config is not for WebDAV backend");
		return { status: BACKEND_STATUS.error, error: "Provided config is not for WebDAV backend" };
	}

	if (os.platform() !== "linux") {
		logger.error("WebDAV mounting is only supported on Linux hosts.");
		return { status: BACKEND_STATUS.error, error: "WebDAV mounting is only supported on Linux hosts." };
	}

	const { status } = await checkHealth(path);
	if (status === "mounted") {
		return { status: BACKEND_STATUS.mounted };
	}

	logger.debug(`Trying to unmount any existing mounts at ${path} before mounting...`);
	await unmount(path);

	const run = async () => {
		await fs.mkdir(path, { recursive: true }).catch((err) => {
			logger.warn(`Failed to create directory ${path}: ${err.message}`);
		});

		const protocol = config.ssl ? "https" : "http";
		const defaultPort = config.ssl ? 443 : 80;
		const port = config.port !== defaultPort ? `:${config.port}` : "";
		const source = `${protocol}://${config.server}${port}${config.path}`;

		const options = config.readOnly
			? ["uid=1000", "gid=1000", "file_mode=0444", "dir_mode=0555", "ro"]
			: ["uid=1000", "gid=1000", "file_mode=0664", "dir_mode=0775"];

		if (config.username && config.password) {
			const secretsFile = "/etc/davfs2/secrets";
			const secretsContent = `${source} ${config.username} ${config.password}\n`;
			await fs.appendFile(secretsFile, secretsContent, { mode: 0o600 });
		}

		logger.debug(`Mounting WebDAV volume ${path}...`);

		const args = ["-t", "davfs", source, path];
		await executeMount(args);

		const { stderr } = await execFile("mount", ["-t", "davfs", "-o", options.join(","), source, path], {
			timeout: OPERATION_TIMEOUT,
			maxBuffer: 1024 * 1024,
		});

		if (stderr?.trim()) {
			logger.warn(stderr.trim());
		}

		logger.info(`WebDAV volume at ${path} mounted successfully.`);
		return { status: BACKEND_STATUS.mounted };
	};

	try {
		return await withTimeout(run(), OPERATION_TIMEOUT, "WebDAV mount");
	} catch (error) {
		const errorMsg = toMessage(error);

		if (errorMsg.includes("already mounted")) {
			return { status: BACKEND_STATUS.mounted };
		}

		logger.error("Error mounting WebDAV volume", { error: errorMsg });

		if (errorMsg.includes("option") && errorMsg.includes("requires argument")) {
			return {
				status: BACKEND_STATUS.error,
				error: "Invalid mount options. Please check your WebDAV server configuration.",
			};
		} else if (errorMsg.includes("connection refused") || errorMsg.includes("Connection refused")) {
			return {
				status: BACKEND_STATUS.error,
				error: "Cannot connect to WebDAV server. Please check the server address and port.",
			};
		} else if (errorMsg.includes("unauthorized") || errorMsg.includes("Unauthorized")) {
			return {
				status: BACKEND_STATUS.error,
				error: "Authentication failed. Please check your username and password.",
			};
		}

		return { status: BACKEND_STATUS.error, error: errorMsg };
	}
};

const unmount = async (path: string) => {
	if (os.platform() !== "linux") {
		logger.error("WebDAV unmounting is only supported on Linux hosts.");
		return { status: BACKEND_STATUS.error, error: "WebDAV unmounting is only supported on Linux hosts." };
	}

	const run = async () => {
		try {
			await fs.access(path);
		} catch (e) {
			logger.warn(`Path ${path} does not exist. Skipping unmount.`, e);
			return { status: BACKEND_STATUS.unmounted };
		}

		await executeUnmount(path);

		await fs.rmdir(path);

		logger.info(`WebDAV volume at ${path} unmounted successfully.`);
		return { status: BACKEND_STATUS.unmounted };
	};

	try {
		return await withTimeout(run(), OPERATION_TIMEOUT, "WebDAV unmount");
	} catch (error) {
		logger.error("Error unmounting WebDAV volume", { path, error: toMessage(error) });
		return { status: BACKEND_STATUS.error, error: toMessage(error) };
	}
};

const checkHealth = async (path: string) => {
	const run = async () => {
		logger.debug(`Checking health of WebDAV volume at ${path}...`);
		await fs.access(path);

		const mount = await getMountForPath(path);

		if (!mount || mount.fstype !== "fuse") {
			throw new Error(`Path ${path} is not mounted as WebDAV.`);
		}

		logger.debug(`WebDAV volume at ${path} is healthy and mounted.`);
		return { status: BACKEND_STATUS.mounted };
	};

	try {
		return await withTimeout(run(), OPERATION_TIMEOUT, "WebDAV health check");
	} catch (error) {
		logger.error("WebDAV volume health check failed:", toMessage(error));
		return { status: BACKEND_STATUS.error, error: toMessage(error) };
	}
};

export const makeWebdavBackend = (config: BackendConfig, path: string): VolumeBackend => ({
	mount: () => mount(config, path),
	unmount: () => unmount(path),
	checkHealth: () => checkHealth(path),
});
