/* TODO: Translate this to a service/module at some point? Something like 'cryto.network' that handles all the internal-networking stuff. */

{ hostname, nodes, pingInterval ? 10 }:
	{ pkgs, lib, ... }: let
		/* NOTE: We cannot just use simple string interpolation, because tinc's configuration format is not indentation-tolerant. Therefore, we do some programmatic concatenation magic to ensure that everything is indentation-free, without turning this file into a mess of wrong indentation. */
		generateConfiguration = options:
			let
				keys = builtins.attrNames options;
				toPairs = map (key: {key = key; value = options.${key};});
				createConfigEntries = map (item: "${item.key} = ${toString item.value}");
			in
				builtins.concatStringsSep "\n" (createConfigEntries (toPairs keys));
	in {
		deployment.secrets = {
			"tinc-key" = {
				source = "../private/${hostname}/tinc-key.priv";
				destination = "/etc/tinc/cryto/ed25519_key.priv";
				owner = { user = "tinc.cryto"; };
				action = [ "systemctl" "restart" "tinc.cryto.service" ]; 
			};
		};

		services.tinc.networks.cryto = {
			debugLevel = 3;
			chroot = false;
			ed25519PrivateKeyFile = "/etc/tinc/cryto/ed25519_key.priv";

			extraConfig = generateConfiguration {
				AutoConnect = "yes";
				PingInterval = pingInterval;
			};

			hosts = let
				escapeHostname = hostname: builtins.replaceStrings [ "." "-" ] [ "_" "_" ] hostname;
				stripSuffix = hostname: builtins.head (builtins.match "^(.+?)\.cryto\.net$" hostname);
				makeMapper = keyMapper: (
					nodeName: nodeConfiguration:
						lib.nameValuePair
							/* NOTE: The below is because for a machine named `foo.bar-baz.net`, tinc expects a configuration file named `foo_bar_baz_net`. */
							# ( builtins.replaceStrings [ "." "-" ] [ "_" "_" ] nodeName )
							( keyMapper nodeName )
							( generateConfiguration {
								# Address = nodeName;
								/* TODO: Figure out why a DNS name doesn't work here ("Error looking up machine-borg2-01.cryto.net port 655: Device or resource busy") */
								Address = nodeConfiguration.ipv4;
								Subnet = "${nodeConfiguration.internalIpv4}/32";
								Ed25519PublicKey = nodeConfiguration.tincPublicKey;
							} )
				);
				# NOTE: We generate both hostname formats and then just use one, since this seems to keep changing... this makes it easier to revert later
				mapperA = makeMapper ( nodeName: escapeHostname (stripSuffix nodeName) );
				mapperB = makeMapper ( nodeName: escapeHostname nodeName );
				hostsA = lib.mapAttrs' mapperA nodes;
				# hostsB = lib.mapAttrs' mapperB nodes;
				hostsB = {};
			in hostsA // hostsB;
		};

		# networking.interfaces."tinc.cryto".ipv4.addresses = [{
		# 	address = nodes.${hostname}.internalIpv4;
		# 	prefixLength = 24;
		# }];

		networking.firewall = {
			allowedTCPPorts = [
				655
			];
			allowedUDPPorts = [
				655
			];
			trustedInterfaces = [
				"tinc.cryto"
			];
		};

		# FIXME: Make the netmask be generated from the prefixLength, instead of hard-coding it
		environment.etc = {
			"tinc/cryto/tinc-up".source = pkgs.writeScript "tinc-up-cryto" ''
				#!${pkgs.stdenv.shell}
				${pkgs.nettools}/bin/ifconfig tinc.cryto ${nodes.${hostname}.internalIpv4} netmask 255.255.255.0
			'';
			"tinc/cryto/tinc-down".source = pkgs.writeScript "tinc-down-cryto" ''
				#!${pkgs.stdenv.shell}
				/run/wrappers/bin/sudo ${pkgs.nettools}/bin/ifconfig tinc.cryto down
			'';
		};

		security.sudo.extraRules = [{
			users    = [ "tinc.cryto" ];
			commands = [{
				command  = "${pkgs.nettools}/bin/ifconfig tinc.cryto down";
				options  = [ "NOPASSWD" ];
			}];
		}];

		# Override this to get rid of the automatic key generation
		systemd.services."tinc.cryto".preStart = lib.mkForce ''
			mkdir -p /etc/tinc/cryto/hosts
			chown tinc.cryto /etc/tinc/cryto/hosts
			mkdir -p /etc/tinc/cryto/invitations
			chown tinc.cryto /etc/tinc/cryto/invitations
		'';
	}
