const dns = require('dns');
const { Netmask } = require('netmask');
const cfg = require('../../../lib/config');
const jwt = require('../../../lib/jwt');
const user = require('../../../lib/db/user');
const ServerUser = require('../../../lib/db/user/User');

async function authorisedIP (req, res) {
	const validIPs = await user.ip({active: true}); // Get all active IP logins
	validIPs.forEach( i => i.$block = new Netmask(i.ip) );
	validIPs.sort( (a, b) => b.$block.bitmask - a.$block.netmask ); // More specific IPs have precedence
	for (const ip of validIPs) {
		const block = ip.$block;
		if (block.contains(req.ip)) {
			const payload = {
				...ip,
				ip: req.ip,
				autologin: true
			};
			delete payload.$block;
			delete payload.hash;
			delete payload.active;
			jwt.issue(payload, req, res);
			return true;
		}
	}
	return false;
}

async function authorisedHost (req, res) {
	const validHosts = await user.host({active: true}); // Get all active host logins
	for (const key in validHosts) {
		try {
			const ip = await dns.promises.resolve(key);
			if (ip == req.ip) {
				const payload = {
					...validHosts[key],
					ip: req.ip,
					autologin: true
				};
				delete payload.$block;
				delete payload.hash;
				delete payload.active;
				jwt.issue(payload, req, res);
				return true;
			}
		} catch (err) {
			if (err.code != "ENODATA") {
				console.error(err);
			}
		}
	}
	return false;
}

// TODO: Check client TLS certificates
// Probably will do this via Nginx with
// ssl_verify_client optional;
// and then putting either of the
// $ssl_client_s_dn or $ssl_client_escaped_cert
// variables into an HTTP header for Node
// to check (naturally, it must be ensured
// that a user cannot just insert the header
// in a request).


async function auth (req, res, next) {

	if (res.headersSent) {
		// Nothing to do, this request must have been
		// handled already by another middleware.
		return;
	}

	// Check for a valid JWT (already decoded by a previous
	// middleware).
	if (req.user) {
		if (!req.user.autologin) {
			// If this is not an automatic login, check if the token is in the
			// second half of its lifetime. If so, reissue a new one, valid for
			// another cfg.jwt.options.expiresIn seconds.
			if (req.user.exp) {
				const ttl = req.user.exp - Date.now()/1000;
				if (ttl < cfg.jwt.options.expiresIn/2) {
					const credentials = await ServerUser.fromSQL(null, req.user.id);
					if (credentials) {
						// Refresh token
						payload = Object.assign({}, credentials.toJSON());
						jwt.issue(Object.assign({}, credentials.toJSON()), req, res);
					}
				}
			}
		}
		next();
		return;
	}

	// Check if the IP is known to us
	if (await authorisedIP(req, res)) {
		next();
		return;
	}

	// Check if the hostname is known to us
	if (await authorisedHost(req, res)) {
		next();
		return;
	}

	next({status: 401, message: "Not authorised"});
}

module.exports = auth;
