{ config, hostName, lib, pkgs, ... }:

with lib;

let
  listenPort = {
    "4" = 51820;
    "6" = 51821;
  };
  wgSubnet = {
    "4" = "2a03:4000:52:ada:2";
    "6" = "2a03:4000:52:ada:3";
  };
  wgSubnetLength = 80;
  wgHostLength = wgSubnetLength + 16;
  batSubnet = "2a03:4000:52:ada:1";
  batSubnetLength = 80;
  batHostLength = batSubnetLength + 16;

  links = mkLinks [
    { from = "vidhar";
      to = "surtr";
      endpointHost = "202.61.241.61";
      PersistentKeepalive = 25;
      family = "4";
    }
    { from = "vidhar";
      to = "surtr";
      endpointHost = "2a03:4000:52:ada::";
      PersistentKeepalive = 25;
      family = "6";
    }
    { from = "sif";
      to = "surtr";
      endpointHost = "202.61.241.61";
      PersistentKeepalive = 25;
      family = "4";
    }
    { from = "sif";
      to = "surtr";
      endpointHost = "2a03:4000:52:ada::";
      PersistentKeepalive = 25;
      family = "6";
    }
    { from = "sif";
      to = "vidhar";
      endpointHost = "10.141.0.1";
      PersistentKeepalive = 25;
      family = "4";
    }
  ];
  wgHostIPs = mapAttrs (_family: wgSubnet: {
    surtr = "${wgSubnet}::/${toString wgHostLength}";
    vidhar = "${wgSubnet}:1::/${toString wgHostLength}";
    sif = "${wgSubnet}:2::/${toString wgHostLength}";
  }) wgSubnet;
  greHostMACPrefixes = {
    "4" = {
      surtr = "02:00:01:00:00";
      vidhar = "02:00:01:00:01";
      sif = "02:00:01:00:02";
    };
    "6" = {
      surtr = "02:00:02:00:00";
      vidhar = "02:00:02:00:01";
      sif = "02:00:02:00:02";
    };
  };
  batHostMACs = {
    surtr = "02:00:00:00:00:00";
    vidhar = "02:00:00:01:00:00";
    sif = "02:00:00:02:00:00";
  };
  batHostIPs = {
    surtr = ["${batSubnet}::/${toString batHostLength}"];
    vidhar = ["${batSubnet}:1::/${toString batHostLength}"];
    sif = ["${batSubnet}:2::/${toString batHostLength}"];
  };
  routers = [ "surtr" ];

  mkPublicKeyPath = family: host: ./hosts + "/${family}" + "/${host}.pub";
  mkPrivateKeyPath = family: host: ./hosts + "/${family}" + "/${host}.priv";

  kernel = config.boot.kernelPackages;

  publicKeyPath = family: mkPublicKeyPath family hostName;
  privateKeyPath = family: mkPrivateKeyPath family hostName;
  inNetwork' = family: pathExists (privateKeyPath family) && pathExists (publicKeyPath family);
  inNetwork = any inNetwork' families;
  hostLinks = filterAttrs (_family: links: links != []) (mapAttrs (_family: filter ({ from, to, ... }: thisHost from || thisHost to)) links);
  linkToPeer = family: opts@{from, to, ...}:
    let
      other = if thisHost from then to else from;
    in {
      AllowedIPs = if elem other routers then ["::/0"] else wgHostIPs.${family}.${other};
      PublicKey = trim (readFile (mkPublicKeyPath family other));
    } // (optionalAttrs (thisHost from) (linkCfgFilterCustom opts // linkMkEndpointCfg family opts));
  linkCfgFilterCustom = filterAttrs (n: _v: !(elem n ["from" "to" "endpointHost"]));
  linkMkEndpointCfg = family: opts@{from, ...}: optionalAttrs (opts ? "endpointHost" && thisHost from) { Endpoint = "${opts.endpointHost}:${toString listenPort.${family}}"; };
  linkToGreDev = family: opts@{from, to, ...}:
    let
      other = if thisHost from then to else from;
    in nameValuePair "yggre-${other}-${family}" {
      netdevConfig = {
        Name = "yggre-${other}-${family}";
        Kind = "ip6gretap";
      };
      tunnelConfig = {
        Local = stripSubnet wgHostIPs.${family}.${hostName};
        Remote = stripSubnet wgHostIPs.${family}.${other};
      };
    };
  linkToGreNetwork = family: ix: opts@{from, to, ...}:
    let
      other = if thisHost from then to else from;
    in nameValuePair "yggre-${other}-${family}" {
      matchConfig = {
        Name = "yggre-${other}-${family}";
      };
      linkConfig = {
        MACAddress = "${greHostMACPrefixes.${family}.${hostName}}:${toHexByte ix}";
        RequiredForOnline = false;
      };
      networkConfig = {
        BatmanAdvanced = "yggdrasil";
        LinkLocalAddressing = "no";
      };
    };
  familyToYggdrasilDev = family: nameValuePair "yggdrasil-wg-${family}" {
    netdevConfig = {
      Name = "yggdrasil-wg-${family}";
      Kind = "wireguard";
    };
    wireguardConfig = {
      PrivateKeyFile = "/run/credentials/systemd-networkd.service/yggdrasil-wg-${family}.priv";
      ListenPort = listenPort.${family};
    };
    wireguardPeers = map (opts@{to, from, ...}: { wireguardPeerConfig = linkToPeer family opts; }) hostLinks.${family};
  };
  familyToLoadCred = family: "yggdrasil-wg-${family}.priv:${config.sops.secrets."yggdrasil-wg-${family}.priv".path}";
  familyToYggdrasilNetwork = family: nameValuePair "yggdrasil-wg-${family}" {
    name = "yggdrasil-wg-${family}";
    matchConfig = {
      Name = "yggdrasil-wg-${family}";
    };
    address = [wgHostIPs.${family}.${hostName}];
    routes = [
      { routeConfig = {
          Destination = "${wgSubnet.${family}}::/${toString wgSubnetLength}";
        };
      }
    ];
    linkConfig = {
      RequiredForOnline = false;
    };
    networkConfig = {
      Tunnel = map (opts@{from, to, ...}: let other = if thisHost from then to else from; in "yggre-${other}-${family}") hostLinks.${family};
    };
  };
  familyToSopsSecret = family: nameValuePair "yggdrasil-wg-${family}.priv" (mkIf (pathExists (privateKeyPath family)) {
    format = "binary";
    sopsFile = privateKeyPath family;
  });

  thisHost = host: host == hostName;
  trim = str: if hasSuffix "\n" str then trim (removeSuffix "\n" str) else str;
  stripSubnet = addr: let matchRes = builtins.match "^(.*)/[0-9]+$" addr; in if matchRes == null then addr else elemAt matchRes 0;
  optIx = optName: xs: let
    withOpts = listToAttrs (imap0 (ix: x: nameValuePair x.name (x.value // { ${optName} = ix; })) (filter (x: x.value.${optName} or false) (imap0 (ix: nameValuePair (toString ix)) xs)));
    withoutOpts = listToAttrs (map (nv: nameValuePair nv.name (removeAttrs nv.value [optName])) (filter (x: !(x.value.${optName} or false)) (imap0 (ix: nameValuePair (toString ix)) xs)));
  in genList (ix: withOpts.${toString ix} or withoutOpts.${toString ix}) (length xs);
  groupFamilies = links: mapAttrs (_name: value: map (filterAttrs (k: _v: k != "family")) value) (groupBy (x: x.family) links);
  mkLinks = groupFamilies;
  families = attrNames links;
  hostFamilies = attrNames hostLinks;
  toHexByte = n: let
    hex = toHexString n;
  in if (stringLength hex < 2) then "0${hex}" else hex;
in {
  config = {
    systemd.network = mkIf inNetwork {
      enable = true;
      netdevs = {
        yggdrasil = {
          netdevConfig = {
            Name = "yggdrasil";
            Kind = "batadv";
          };
          extraConfig = ''
            [BatmanAdvanced]
            Fragmentation=no
          '';
        };
      } // listToAttrs (map familyToYggdrasilDev hostFamilies) // listToAttrs (concatMap (family: map (linkToGreDev family) hostLinks.${family}) hostFamilies);

      networks = {
        yggdrasil = {
          name = "yggdrasil";
          matchConfig = {
            Name = "yggdrasil";
          };
          address = batHostIPs.${hostName};
          dns = ["2a03:4000:52:ada:1:1::"];
          domains = ["yggdrasil"];
          routes = [
            { routeConfig = {
                Destination = "${batSubnet}::/${toString batSubnetLength}";
                GatewayOnLink = true;
              };
            }
            { routeConfig = {
                Destination = "${batSubnet}::/${toString batSubnetLength}";
                GatewayOnLink = true;
                Table = "yggdrasil";
              };
            }
            { routeConfig = {
                Destination = batHostIPs.${hostName};
                GatewayOnLink = true;
                Table = "yggdrasil";
              };
            }
          ] ++ (concatMap (router: map (rAddr: { routeConfig = { Destination = "::/0"; Gateway = stripSubnet rAddr; GatewayOnLink = true; Table = "yggdrasil"; }; }) batHostIPs.${router}) (filter (router: router != hostName) routers));
          routingPolicyRules = map (addr: { routingPolicyRuleConfig = { Table = "yggdrasil"; From = addr; Priority = 1; }; }) batHostIPs.${hostName};
          linkConfig = {
            MACAddress = "${batHostMACs.${hostName}}";
            RequiredForOnline = false;
            MTUBytes = "1280";
          };
          networkConfig = {
            LLMNR = false;
            MulticastDNS = false;
            DNSSEC = false;
          };
        };
      } // listToAttrs (map familyToYggdrasilNetwork hostFamilies) // listToAttrs (concatMap (family: imap0 (linkToGreNetwork family) hostLinks.${family}) hostFamilies);

      config.routeTables.yggdrasil = 1024;
    };

    systemd.services."systemd-networkd".serviceConfig.LoadCredential = mkIf inNetwork (map familyToLoadCred hostFamilies);

    sops.secrets = listToAttrs (map familyToSopsSecret hostFamilies);

    boot.extraModulePackages = optional (versionOlder kernel.kernel.version "5.6") kernel.wireguard ++ [kernel.batman_adv];
    environment.systemPackages = with pkgs; [ wireguard-tools batctl ];
    networking.networkmanager.unmanaged = ["yggdrasil" "ip6gre0" "ip6tnl0"] ++ map (family: "yggdrasil-wg-${family}") hostFamilies ++ concatMap (family: map ({from, to, ...}: let other = if thisHost from then to else from; in "yggre-${other}-${family}") hostLinks.${family}) hostFamilies;
  };
}