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

let
  defaultGateways = {
    serv = "serv-gw";
    c3d2 = "c3d2-gw3";
    pub = "pub-gw";
    flpk = "flpk-gw";
  };

  inherit (config.networking) hostName;
  inherit (config.c3d2.deployment) server;
  serverFQDN = "${server}.cluster.zentralwerk.org";

  generateMacAddress = net:
    let
      hash = builtins.hashString "md5" "1-${net}-${hostName}";
      c = off: builtins.substring off 2 hash;
    in
      "${builtins.substring 0 1 hash}2:${c 2}:${c 4}:${c 6}:${c 8}:${c 10}";

  nets = builtins.attrNames (
    lib.filterAttrs (_: { hosts4, hosts6, ... }:
      hosts4 ? ${hostName} ||
      lib.filterAttrs (_: hosts6:
        hosts6 ? ${hostName}
      ) hosts6 != {}
    ) zentralwerk.lib.config.site.net
  );

  arch-to-host = rec {
    server9 = "westmere";
    server10 = "ivybridge";
    nomad = server9;
  };
in
{
  options.c3d2.deployment = with lib; {
    server = mkOption {
      type = types.enum [ "server9" "server10" ];
      default = null;
      description = ''
        Server that is supposed to host this MicroVM.
      '';
    };

    autoNetSetup = mkOption {
      type = types.bool;
      default = true;
      description = ''
        Automatically configure MicroVM network interfaces and
        systemd-networkd according to Zentralwerk network data.
      '';
    };

    mounts = mkOption {
      description = "Persistent filesystems to create, without leading /.";
      type = with types; listOf str;
    };

    mountBase = mkOption {
      description = "Location (ZFS dataset, ...) where all the shares live.";
      type = types.path;
      default = "/var/lib/microvms/${hostName}";
    };
  };

  config = {
    c3d2.deployment.mounts = [ "etc" "home" "var" ];

    # make mounts like /etc /home /var available early so that they can be used in system.activationScripts
    fileSystems = lib.genAttrs (map (x: "/" + x) config.c3d2.deployment.mounts)
      (_: { neededForBoot = true; });

    microvm = {
      hypervisor = lib.mkDefault "cloud-hypervisor";
      mem = lib.mkDefault 512;
      vcpu = lib.mkDefault 4;

      interfaces = lib.mkIf config.c3d2.deployment.autoNetSetup (
        map (net: {
          type = "tap";
          id = builtins.substring 0 15 "${net}-${hostName}";
          mac = generateMacAddress net;
        }) nets
      );

      shares = [ {
        source = "/nix/store";
        mountPoint = "/nix/.ro-store";
        tag = "store";
        proto = "virtiofs";
        socket = "store.socket";
      } ]
      ++ map (dir:
        if lib.hasPrefix "/" dir
        then throw "${dir} starts with a leading /. Just don't!"
        else let
          tag = builtins.replaceStrings ["/"] ["_"] dir;
        in {
          source = "${config.c3d2.deployment.mountBase}/${dir}";
          mountPoint = "/${dir}";
          inherit tag;
          proto = "virtiofs";
          socket = "${tag}.socket";
        }) config.c3d2.deployment.mounts;
    };

    networking = lib.mkIf config.c3d2.deployment.autoNetSetup {
      useDHCP = false;
      dhcpcd.enable = false;
      useNetworkd = true;
    };

    systemd.network = lib.mkIf config.c3d2.deployment.autoNetSetup {
      links = builtins.foldl' (links: net: links // {
        "30-${net}" = {
          # enable = true;
          matchConfig.MACAddress = generateMacAddress net;
          # rename interface to net name
          linkConfig.Name = net;
        };
      }) {} nets;

      networks = builtins.foldl' (networks: net: networks // {
        "30-${net}" =
          let
            zwNet = zentralwerk.lib.config.site.net.${net};
            addresses =
              lib.optional (zwNet.hosts4 ? ${hostName}) "${zwNet.hosts4.${hostName}}/${toString zwNet.subnet4Len}"
              ++
              map (hosts6: "${hosts6.${hostName}}/64") (
                builtins.filter (hosts6: hosts6 ? ${hostName}) (
                  builtins.attrValues zwNet.hosts6
                )
              );
          in {
            matchConfig.MACAddress = generateMacAddress net;
            addresses = map (Address: {
              addressConfig = { inherit Address; };
            }) addresses;
            gateway = lib.mkIf (defaultGateways ? ${net}) (
              let
                gw = defaultGateways.${net};
              in
                [ zwNet.hosts4.${gw} ]
                ++ map (hosts6: hosts6.${gw}) (
                  builtins.filter (hosts6: hosts6 ? ${gw}) (
                    builtins.attrValues zwNet.hosts6
                  )
                )
            );
          };
      }) {} nets;
    };

    simd.arch = arch-to-host.${config.c3d2.deployment.server};

    system.build = {
      copyToServer = pkgs.writeShellScript "copy-to-${server}" ''
        nix copy --no-check-sigs --to ssh-ng://root@${serverFQDN} $@
      '';

      runOnServer = pkgs.writeShellScript "run-on-${server}" ''
        # we cannot execute any other commands here because it grabs away $@
        ssh root@${serverFQDN} -- $@
      '';
    };
  };
}
