import ip from "ip";
import { Config } from "./config";
import { IPSetHelper } from "./ipset";
import _ from "underscore";
import bunyan from "bunyan";

function testRange(addr: string, range: string[]) {
	if (!range) {
		return true;
	}
	return _.any(range, r => {
		return ip.cidrSubnet(r).contains(addr)
	});
}

export interface ReturnMessage {
	success: boolean,
	message: string
}

export class Selector {
	config: Config;
	log: bunyan;
	constructor(config: Config) {
		this.config = config;
		this.log = bunyan.createLogger({
			name: "gateway-selector"
		});
	}
	async init() {
		for (let group of this.config.groups) {
			for (let set of group.sets) {
				if (!await IPSetHelper.create(set.setname)) {
					this.log.info(`IPSet ${set.setname} created.`);
				}

			}
		}
	}
	async get(addr: string) {
		const groups = this.config.groups.filter(g => testRange(addr, g.allowedIPs)).map(g => _.clone(g));
		for (let group of groups) {
			const sets = group.sets.filter(s => testRange(addr, s.allowedIPs)).map(s => _.clone(s));
			group.allowedIPs = null;
			for (let set of sets) {
				set.here = await IPSetHelper.test(set.setname, addr);
				set.allowedIPs = null;
			}
			group.sets = sets;
		}
		return groups;
	}
	async add(addr: string, setname: string): Promise<ReturnMessage> {
		const group = this.config.groups.find(g => g.sets.find(s => s.setname === setname));
		if (!group) {
			return { success: false, message: "Not found." };
		}
		if (!testRange(addr, group.allowedIPs)) {
			return { success: false, message: "Forbidden." };
		}
		const set = group.sets.find(s => s.setname === setname);
		if (!set) {
			return { success: false, message: "Not found." };
		}
		if (!testRange(addr, set.allowedIPs)) {
			return { success: false, message: "Forbidden." };
		}
		if (await IPSetHelper.test(set.setname, addr)) {
			return { success: true, message: null };
		}
		const otherSets = group.sets.filter(s => s.setname !== setname);
		for (let oset of otherSets) {
			if (await IPSetHelper.test(oset.setname, addr)) {
				await IPSetHelper.del(oset.setname, addr);
			}
		}
		const message = await IPSetHelper.add(set.setname, addr);
		this.log.info(`ADD: ${addr} ${set.name} => ${message}`);
		return { success: !message, message };
	}
	async del(addr: string, setname: string): Promise<ReturnMessage> {
		const group = this.config.groups.find(g => g.sets.find(s => s.setname === setname));
		if (!group) {
			return { success: false, message: "Not found." };
		}
		if (!testRange(addr, group.allowedIPs)) {
			return { success: false, message: "Forbidden." };
		}
		const set = group.sets.find(s => s.setname === setname);
		if (!set) {
			return { success: false, message: "Not found." };
		}
		if (!testRange(addr, set.allowedIPs)) {
			return { success: false, message: "Forbidden." };
		}
		if (!await IPSetHelper.test(set.setname, addr)) {
			return { success: true, message: null };
		}
		const message = await IPSetHelper.del(set.setname, addr);
		this.log.info(`DEL: ${addr} ${set.name} => ${message}`);
		return { success: !message, message };
	}
}
