import { createLogger } from "../utils/logger/index.js";
import { init_logger } from "../utils/logger.js";
import fs from "node:fs";
import net from "node:net";
import { execFileSync } from "node:child_process";
//#region src/gateway/ports.ts
/**
* Ports Management - Port management utilities
*/
init_logger();
const log = createLogger("Ports");
function parseLsofOutput(output) {
	const lines = output.split(/\r?\n/).filter(Boolean);
	const results = [];
	let current = {};
	for (const line of lines) if (line.startsWith("p")) {
		if (current.pid) results.push(current);
		current = { pid: parseInt(line.slice(1), 10) };
	} else if (line.startsWith("c")) current.command = line.slice(1);
	if (current.pid) results.push(current);
	return results;
}
/**
* Parse `ss -tlnp` output to find PIDs listening on a given port.
* Example line:
*   LISTEN 0 128 0.0.0.0:3000 0.0.0.0:* users:(("node",pid=1234,fd=18))
*/
function listPortListenersViaSs(port) {
	let out;
	try {
		out = execFileSync("ss", ["-tlnp", `sport = :${port}`], { encoding: "utf-8" });
	} catch (err) {
		if (err.status === 1) return [];
		throw err instanceof Error ? err : new Error(String(err));
	}
	const results = [];
	for (const line of out.split(/\r?\n/)) {
		if (!line.includes("LISTEN")) continue;
		for (const match of line.matchAll(/pid=(\d+)/g)) {
			const pid = parseInt(match[1], 10);
			if (!results.some((p) => p.pid === pid)) results.push({ pid });
		}
	}
	return results;
}
/**
* Read /proc/net/tcp (and /proc/net/tcp6) to find PIDs listening on a given port.
* Falls back to an empty list if /proc is unavailable (non-Linux).
*/
function listPortListenersViaProc(port) {
	const hexPort = port.toString(16).toUpperCase().padStart(4, "0");
	const results = [];
	const inodeSet = /* @__PURE__ */ new Set();
	for (const procFile of ["/proc/net/tcp", "/proc/net/tcp6"]) {
		let content;
		try {
			content = fs.readFileSync(procFile, "utf-8");
		} catch {
			continue;
		}
		for (const line of content.split("\n").slice(1)) {
			const parts = line.trim().split(/\s+/);
			if (parts.length < 10 || parts[3] !== "0A") continue;
			if (parts[1].split(":")[1]?.toUpperCase() !== hexPort) continue;
			inodeSet.add(parts[9]);
		}
	}
	if (inodeSet.size === 0) return results;
	let pidDirs;
	try {
		pidDirs = fs.readdirSync("/proc").filter((name) => /^\d+$/.test(name));
	} catch {
		return results;
	}
	for (const pidStr of pidDirs) {
		const fdDir = `/proc/${pidStr}/fd`;
		let fds;
		try {
			fds = fs.readdirSync(fdDir);
		} catch {
			continue;
		}
		for (const fd of fds) {
			let target;
			try {
				target = fs.readlinkSync(`${fdDir}/${fd}`);
			} catch {
				continue;
			}
			const inodeMatch = /^socket:\[(\d+)\]$/.exec(target);
			if (!inodeMatch || !inodeSet.has(inodeMatch[1])) continue;
			const pid = parseInt(pidStr, 10);
			if (!results.some((p) => p.pid === pid)) {
				let command;
				try {
					command = fs.readFileSync(`/proc/${pidStr}/comm`, "utf-8").trim();
				} catch {}
				results.push({
					pid,
					command
				});
			}
			break;
		}
	}
	return results;
}
/**
* Parse `netstat -ano` output (Windows) to find PIDs listening on a given port.
* Example line:
*   TCP    0.0.0.0:18790          0.0.0.0:0              LISTENING       1234
*/
function parseNetstatOutput(output, port) {
	const portSuffix = `:${port}`;
	const results = [];
	for (const line of output.split(/\r?\n/)) {
		if (!line.includes("LISTENING")) continue;
		const parts = line.trim().split(/\s+/);
		if (parts.length < 5) continue;
		if (!parts[1].endsWith(portSuffix)) continue;
		const pid = parseInt(parts[parts.length - 1], 10);
		if (Number.isFinite(pid) && pid > 0 && !results.some((p) => p.pid === pid)) results.push({ pid });
	}
	return results;
}
function listPortListenersViaNetstat(port) {
	let out;
	try {
		out = execFileSync("netstat", [
			"-ano",
			"-p",
			"TCP"
		], {
			encoding: "utf-8",
			shell: true,
			timeout: 5e3
		});
	} catch {
		return [];
	}
	return parseNetstatOutput(out, port);
}
function listPortListeners(port) {
	if (process.platform === "win32") return listPortListenersViaNetstat(port);
	try {
		return parseLsofOutput(execFileSync("lsof", [
			"-nP",
			`-iTCP:${port}`,
			"-sTCP:LISTEN",
			"-FpFc"
		], { encoding: "utf-8" }));
	} catch (err) {
		const execErr = err;
		if (execErr.code !== "ENOENT") {
			if (execErr.status === 1) return [];
			throw err instanceof Error ? err : new Error(String(err));
		}
		log.debug({ port }, "lsof not found; trying ss fallback");
	}
	try {
		return listPortListenersViaSs(port);
	} catch (err) {
		if (err.code !== "ENOENT") throw err instanceof Error ? err : new Error(String(err));
		log.debug({ port }, "ss not found; trying /proc/net/tcp fallback");
	}
	return listPortListenersViaProc(port);
}
async function forceFreePortAndWait(port, opts = {}) {
	const timeoutMs = Math.max(opts.timeoutMs ?? 2e3, 0);
	const intervalMs = Math.max(opts.intervalMs ?? 100, 1);
	const sigtermTimeoutMs = Math.min(Math.max(opts.sigtermTimeoutMs ?? 700, 0), timeoutMs);
	const listeners = listPortListeners(port);
	const killed = [...listeners];
	for (const proc of listeners) try {
		process.kill(proc.pid, "SIGTERM");
		log.info({ pid: proc.pid }, "Sent SIGTERM");
	} catch (err) {
		log.warn({
			pid: proc.pid,
			err
		}, "Failed to send SIGTERM");
	}
	let waitedMs = 0;
	const checkInterval = () => new Promise((r) => setTimeout(r, intervalMs));
	const sigtermTries = Math.ceil(sigtermTimeoutMs / intervalMs);
	for (let i = 0; i < sigtermTries; i++) {
		await checkInterval();
		waitedMs += intervalMs;
		if (listPortListeners(port).length === 0) return {
			killed,
			waitedMs,
			escalatedToSigkill: false
		};
	}
	const remaining = listPortListeners(port);
	for (const proc of remaining) try {
		process.kill(proc.pid, "SIGKILL");
		log.info({ pid: proc.pid }, "Sent SIGKILL");
	} catch (err) {
		log.warn({
			pid: proc.pid,
			err
		}, "Failed to send SIGKILL");
	}
	const remainingBudget = Math.max(timeoutMs - waitedMs, 0);
	const sigkillTries = Math.ceil(remainingBudget / intervalMs);
	for (let i = 0; i < sigkillTries; i++) {
		await checkInterval();
		waitedMs += intervalMs;
		if (listPortListeners(port).length === 0) return {
			killed,
			waitedMs,
			escalatedToSigkill: true
		};
	}
	throw new Error(`Port ${port} still has listeners after force free`);
}
async function checkPortAvailable(port, host = "0.0.0.0") {
	return new Promise((resolve) => {
		const server = net.createServer();
		server.once("error", (err) => {
			if (err.code === "EADDRINUSE") resolve(false);
			else resolve(true);
		});
		server.once("listening", () => {
			server.close();
			resolve(true);
		});
		server.listen(port, host);
	});
}
//#endregion
export { checkPortAvailable, forceFreePortAndWait, listPortListeners, parseLsofOutput, parseNetstatOutput };

//# sourceMappingURL=ports.js.map