{ 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 = {
    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 = {
      mergeSettings = lib.mkEnableOption ''
        Whether to merge <literal>c3d2.nncp.<…>.nncp</literal>
        into <literal>programs.nncp.settings</literal>.
      '';
      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.nncp.mergeSettings 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"
            ];
            # get by running mkpasswd logged in as the user
            hashedPassword = "$y$j9T$AoK/PRviZS4BDJ6jX/Qt6/$FDM/JfANEU7H0RAIuN0DL2hjYujVAVDdI0jgN5wGwB5";
            openssh.authorizedKeys.keys = adminKeys;
          };

          # TODO: change when on 23.05
          # https://github.com/NixOS/nixpkgs/blob/master/nixos/modules/profiles/installation-device.nix#L23
          # nixos = lib.mkIf (config.system.nixos.variant_id == "installer") { openssh.authorizedKeys.keys = adminKeys; };
          # using proxy option to detect iso
          # https://github.com/NixOS/nixpkgs/blob/nixos-22.11/nixos/modules/profiles/installation-device.nix#L48
          nixos = lib.mkIf (config.services.getty.autologinUser == "nixos") { 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;
    };
  };
}
