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

let
  cfg = config.c3d2;

  hqPrefix64 = lib.removeSuffix "::" (builtins.head (
    builtins.split "/" zentralwerk.lib.config.site.net.c3d2.subnets6.dn42
  ));

  neighMod = with lib; types.submodule {
    options = {
      addrs = mkOption {
        type = with types; attrsOf str;
        default = { };
      };
      via = mkOption
        {
          type = with types; listOf str;
          default = [ ];
        };
    } // (with builtins; let value = mkOption { type = types.str; }; in
    listToAttrs (map (name: { inherit name value; }) [ "exchpub" "id" "noisepub" "signpub" ]));
  };

  # Generate a deterministic IPv6 address for a 64 bit prefix
  # and seed string. Prefix must not contain trailing ':'.
  toIpv6Address = prefix64: seed:
    with builtins;
    let
      digest = builtins.hashString "sha256" seed;
      hextets = map (i: substring (4 * i) 4 digest) [ 0 1 2 3 ];
    in
    concatStringsSep ":" ([ prefix64 ] ++ hextets);

  # Generate a deterministic public IPv6 addresses
  # for the HQ networking using a seed string.
  toHqPrivateAddress = toIpv6Address hqPrefix64;
in
{
  options.c3d2 = {
    mergeNncpSettings = lib.mkEnableOption ''
      Whether to merge <literal>c3d2.nncp.<…>.nncp</literal>
      into <literal>programs.nncp.settings</literal>.
    '';

    k-ot.enable = lib.mkEnableOption ''
      Add k-ot user to this machine. Anyone with an SSH key listed in
      <literal>c3d2.users</literal> can log in as this user.
    '';

    hq = {
      interface = lib.mkOption {
        type = with lib.types; nullOr str;
        default = null;
        example = "eth0";
        description = ''
          Configure the given interface name with an internal IP address.
        '';
      };

      journalToMqtt = lib.mkOption {
        type = lib.types.bool;
        # broken :(
        default = false;
      };
    };

    nncp.neigh = lib.mkOption {
      type = with lib.types; attrsOf neighMod;
      default = { };
      description = ''
        Attrset of NNCP neighbours for relaying packets.
        User endpoints go in <literal>c3d2.users</literal>.
      '';
    };

    sshKeys = lib.mkOption {
      type = with lib.types; attrsOf (listOf str);
      default = [ ];
    };
  };

  config =    {
      programs.nncp.settings = lib.optionalAttrs cfg.mergeNncpSettings cfg.nncp;

      users =
        let
          adminKeys = with builtins; lib.lists.flatten (attrValues cfg.sshKeys);
        in
        {
          users = {
            k-ot = lib.mkIf cfg.k-ot.enable {
              createHome = true;
              isNormalUser = true;
              uid = 1000;
              extraGroups = [
                "audio"
                "video"
                "wheel"
              ];
              password = "k-otk-ot";
              openssh.authorizedKeys.keys = adminKeys;
            };

            root.openssh.authorizedKeys.keys = adminKeys;
          };
        };

      services.vector = lib.mkIf config.c3d2.hq.journalToMqtt {
        enable = true;
        journaldAccess = true;
        settings = {
          sources.journal = {
            type = "journald";
            current_boot_only = true;
          };
          sinks.mqtt = {
            inputs = [ "journal" ];
            type = "mqtt";
            host = "broker.serv.zentralwerk.org";
            # port = 8883;
            user = "SECRET[mqtt.user]";
            password = "SECRET[mqtt.password]";
            client_id = "vector-${config.networking.hostName}";
            encoding.codec = "json";
            topic = "journal/{{ host }}/{{ _SYSTEMD_UNIT }}/{{ PRIORITY }}";
            # tls.enabled = true;
            # tls.ca_file = "/etc/ssl/certs/ca-certificates.crt";
          };
          secret.mqtt =
            let
              catSecrets = pkgs.writeScript "cat-vector-secrets" ''
                #!${pkgs.runtimeShell} -e
                echo '{'
                COMMA=n
                for F in $@; do
                  if [ $COMMA = y ]; then
                    echo '  ,'
                  else
                    COMMA=y
                  fi

                  echo '  "'$(basename $F)'": {"value": "'$(cat $F)'", "error": null }'
                done
                echo '}'
              '';
            in
            {
              type = "exec";
              command = [
                catSecrets
                config.sops.secrets."mqtt/user".path
                config.sops.secrets."mqtt/password".path
              ];
            };
        };
      };

      sops.secrets = lib.mkIf config.c3d2.hq.journalToMqtt {
        "mqtt/user" = {
          sopsFile = ../modules/mqtt.yaml;
          owner = config.systemd.services.vector.serviceConfig.User;
        };
        "mqtt/password" = {
          sopsFile = ../modules/mqtt.yaml;
          owner = config.systemd.services.vector.serviceConfig.User;
        };
      };

      systemd.network.networks = lib.mkIf (cfg.hq.interface != null && config.networking.useNetworkd) {
        "40-eth0".routes = [{
          routeConfig = {
            Gateway = "172.22.99.4";
            GatewayOnLink = true;
          };
        }];
      };

      networking = {
        interfaces = lib.mkIf (cfg.hq.interface != null) {
          "${cfg.hq.interface}".ipv6.addresses = [{
            address = toHqPrivateAddress config.networking.hostName;
            prefixLength = 64;
          }];
        };

        nameservers = with hostRegistry.dnscache; [
          ip4
          ip6
          "9.9.9.9"
        ];
        useHostResolvConf = lib.mkIf (!config.services.resolved.enable) true;
      };

      environment.etc."resolv.conf" = lib.mkIf (!config.services.resolved.enable) {
        text = lib.concatMapStrings
          (ns: ''
            nameserver ${ns}
          '')
          config.networking.nameservers;
      };
    };
}
