/**
 * Network utilities — remote detection, port binding, browser opening.
 * isRemoteSession, getServerPort, listenOnPort, openBrowser
 */

import { spawn } from "node:child_process";
import type { Server } from "node:http";
import { release } from "node:os";

const DEFAULT_REMOTE_PORT = 19432;
const LOOPBACK_HOST = "127.0.0.1";

/**
 * Check if running in a remote session (SSH, devcontainer, etc.)
 * Honors PLANNOTATOR_REMOTE as a tri-state override, or detects SSH_TTY/SSH_CONNECTION.
 */
function getRemoteOverride(): boolean | null {
	const remote = process.env.PLANNOTATOR_REMOTE;
	if (remote === undefined) {
		return null;
	}

	if (remote === "1" || remote?.toLowerCase() === "true") {
		return true;
	}

	if (remote === "0" || remote?.toLowerCase() === "false") {
		return false;
	}

	return null;
}

export function isRemoteSession(): boolean {
	const remoteOverride = getRemoteOverride();
	if (remoteOverride !== null) {
		return remoteOverride;
	}
	// Legacy SSH detection
	if (process.env.SSH_TTY || process.env.SSH_CONNECTION) {
		return true;
	}
	return false;
}

/**
 * Get the server port to use.
 * - PLANNOTATOR_PORT env var takes precedence
 * - Remote sessions default to 19432 (for port forwarding)
 * - Local sessions use random port
 * Returns { port, portSource } so caller can notify user if needed.
 */
export function getServerPort(): {
	port: number;
	portSource: "env" | "remote-default" | "random";
} {
	const envPort = process.env.PLANNOTATOR_PORT;
	if (envPort) {
		const parsed = parseInt(envPort, 10);
		if (!Number.isNaN(parsed) && parsed > 0 && parsed < 65536) {
			return { port: parsed, portSource: "env" };
		}
		// Invalid port - fall back silently, caller can check env var themselves
	}
	if (isRemoteSession()) {
		return { port: DEFAULT_REMOTE_PORT, portSource: "remote-default" };
	}
	return { port: 0, portSource: "random" };
}

export function getServerHostname(): string {
	return isRemoteSession() ? "0.0.0.0" : LOOPBACK_HOST;
}

const MAX_RETRIES = 5;
const RETRY_DELAY_MS = 500;

export async function listenOnPort(
	server: Server,
): Promise<{ port: number; portSource: "env" | "remote-default" | "random" }> {
	const result = getServerPort();

	for (let attempt = 1; attempt <= MAX_RETRIES; attempt++) {
		try {
			await new Promise<void>((resolve, reject) => {
				server.once("error", reject);
				server.listen(
					result.port,
					getServerHostname(),
					() => {
						server.removeListener("error", reject);
						resolve();
					},
				);
			});
			const addr = server.address() as { port: number };
			return { port: addr.port, portSource: result.portSource };
		} catch (err: unknown) {
			const isAddressInUse =
				err instanceof Error && err.message.includes("EADDRINUSE");
			if (isAddressInUse && attempt < MAX_RETRIES) {
				await new Promise((r) => setTimeout(r, RETRY_DELAY_MS));
				continue;
			}
			if (isAddressInUse) {
				const hint = isRemoteSession()
					? " (set PLANNOTATOR_PORT to use a different port)"
					: "";
				throw new Error(
					`Port ${result.port} in use after ${MAX_RETRIES} retries${hint}`,
				);
			}
			throw err;
		}
	}

	// Unreachable, but satisfies TypeScript
	throw new Error("Failed to bind port");
}

/**
 * Open URL in system browser (Node-compatible, no Bun $ dependency).
 * Honors PLANNOTATOR_BROWSER and BROWSER env vars.
 * Returns { opened: true } if browser was opened, { opened: false, isRemote: true, url } if remote session.
 */
export function openBrowser(url: string): {
	opened: boolean;
	isRemote?: boolean;
	url?: string;
} {
	const browser = process.env.PLANNOTATOR_BROWSER || process.env.BROWSER;
	if (isRemoteSession() && !browser) {
		return { opened: false, isRemote: true, url };
	}

	try {
		const platform = process.platform;
		const wsl =
			platform === "linux" && release().toLowerCase().includes("microsoft");

		let cmd: string;
		let args: string[];

		if (browser) {
			if (process.env.PLANNOTATOR_BROWSER && platform === "darwin") {
				cmd = "open";
				args = ["-a", browser, url];
			} else if (platform === "win32" || wsl) {
				cmd = "cmd.exe";
				args = ["/c", "start", "", browser, url];
			} else {
				cmd = browser;
				args = [url];
			}
		} else if (platform === "win32" || wsl) {
			cmd = "cmd.exe";
			args = ["/c", "start", "", url];
		} else if (platform === "darwin") {
			cmd = "open";
			args = [url];
		} else {
			cmd = "xdg-open";
			args = [url];
		}

		const child = spawn(cmd, args, { detached: true, stdio: "ignore" });
		child.once("error", () => {});
		child.unref();
		return { opened: true };
	} catch {
		return { opened: false };
	}
}
