const cookie = require("cookie");
const { get_conf, get_redis_subscriber } = require("../../node_utils");
const { get_url } = require("../utils");
const conf = get_conf();
const redisClient = get_redis_subscriber("redis_queue");

async function getSecretFromRedis() {
	if (!redisClient.isOpen) await redisClient.connect();
	const val = await redisClient.get("socketio_auth_secret");
	return val;
}

function authenticate_with_frappe(socket, next) {
	let namespace = socket.nsp.name;
	namespace = namespace.slice(1, namespace.length); // remove leading `/`

	if (namespace != get_site_name(socket)) {
		next(new Error("Invalid namespace"));
	}

	if (get_hostname(socket.request.headers.host) != get_hostname(socket.request.headers.origin)) {
		next(new Error("Invalid origin"));
		return;
	}

	if (!socket.request.headers.cookie && !socket.request.headers.authorization) {
		next(
			new Error(
				"Missing cookie and authorization header. Either one needed for authentication."
			)
		);
		return;
	}

	let cookies = cookie.parse(socket.request.headers.cookie || "");
	let authorization_header = socket.request.headers.authorization;

	if (!cookies.sid && !authorization_header) {
		next(new Error("No authentication method used. Use cookie or authorization header."));
		return;
	}
	socket.sid = cookies.sid;
	socket.authorization_header = authorization_header;

	socket.frappe_request = async (path, args = {}, opts = {}) => {
		let query_args = new URLSearchParams(args);
		if (query_args.toString()) {
			path = path + "?" + query_args.toString();
		}

		let headers = {};
		if (socket.authorization_header) {
			headers["Authorization"] = socket.authorization_header;
		} else if (socket.sid) {
			headers["Cookie"] = `sid=${socket.sid}`;
		}
		const secret = await getSecretFromRedis();
		if (secret) {
			headers["X-Frappe-Socket-Secret"] = secret;
		}
		return fetch(get_url(socket, path), {
			...opts,
			headers,
		});
	};

	socket
		.frappe_request("/api/method/frappe.realtime.get_user_info")
		.then((res) => res.json())
		.then(async ({ message }) => {
			if (socket.user !== "Guest" && !message.installed_apps) {
				const retry_res = await socket.frappe_request(
					"/api/method/frappe.realtime.get_user_info"
				);
				const retry_data = await retry_res.json();
				message = retry_data.message;
			}

			socket.user = message.user;
			socket.user_type = message.user_type;
			socket.installed_apps = message.installed_apps || [];
			next();
		})
		.catch((e) => {
			next(new Error(`Unauthorized: ${e}`));
		});
}

function get_site_name(socket) {
	if (socket.site_name) {
		return socket.site_name;
	} else if (socket.request.headers["x-frappe-site-name"]) {
		socket.site_name = get_hostname(socket.request.headers["x-frappe-site-name"]);
	} else if (
		conf.default_site &&
		["localhost", "127.0.0.1"].indexOf(get_hostname(socket.request.headers.host)) !== -1
	) {
		socket.site_name = conf.default_site;
	} else if (socket.request.headers.origin) {
		socket.site_name = get_hostname(socket.request.headers.origin);
	} else {
		socket.site_name = get_hostname(socket.request.headers.host);
	}
	return socket.site_name;
}

function get_hostname(url) {
	if (!url) return undefined;
	if (url.indexOf("://") > -1) {
		url = url.split("/")[2];
	}
	return url.match(/:/g) ? url.slice(0, url.indexOf(":")) : url;
}

module.exports = authenticate_with_frappe;
