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

with lib;

let
  reverseDomain = domain: concatStringsSep "." (reverseList (splitString "." domain));

  acmeChallengeZonefile = domain: pkgs.writeText "${reverseDomain "_acme-challenge.${domain}"}.soa" ''
    $ORIGIN _acme-challenge.${domain}.
    $TTL 3600
    @ IN SOA ns.yggdrasil.li. root.yggdrasil.li. 2022022103 10800 3600 604800 30

      IN NS  ns.yggdrasil.li.
  '';

  knotKeys = let
    dir = ./keys;
    toKeyInfo = name: v:
      if v == "regular" || v == "symlink"
      then { path = dir + "/${name}"; inherit name; }
      else null;
  in filter (v: v != null) (mapAttrsToList toKeyInfo (builtins.readDir dir));

  indentString = indentation: str: concatMapStringsSep "\n" (str: "  ${str}") (splitString "\n" (removeSuffix "\n" str));

  mkZone = {domain, path ? (./zones + "/${reverseDomain domain}.soa"), acmeDomains ? [domain], addACLs ? {}}: indentString "  " (let
    keys = acmeDomain: [(assert (config.sops.secrets ? "${acmeDomain}_acme.yaml"); "${acmeDomain}_acme_acl")] ++ (addACLs.${acmeDomain} or []);
  in ''
    - domain: ${domain}
      template: inwx_zone
      ${optionalString (acmeDomains != []) "acl: [local_acl, inwx_acl]"}
      file: ${path}
    ${concatMapStringsSep "\n" (acmeDomain: ''
      - domain: _acme-challenge.${acmeDomain}
        template: acme_zone
        acl: [${concatStringsSep ", " (keys acmeDomain)}]
        file: ${acmeChallengeZonefile acmeDomain}
    '') acmeDomains}
  '');
in {
  config = {
    fileSystems."/var/lib/knot" =
      { device = "surtr/safe/var-lib-knot";
        fsType = "zfs";
      };

    systemd.services.knot.unitConfig.RequiresMountsFor = [ "/var/lib/knot" ];
    
    services.knot = {
      enable = true;
      keyFiles = map ({name, ...}: config.sops.secrets.${name}.path) knotKeys;
      extraConfig = ''
        server:
          listen: 127.0.0.1@53
          listen: ::1@53
          listen: 202.61.241.61@53
          listen: 2a03:4000:52:ada::@53

        remote:
          - id: inwx_notify
            address: 2a0a:c980::53
            via: 2a03:4000:52:ada::
          - id: recursive
            address: ::1@5353
          - id: local
            address: ::1@53
            key: local_key

        acl:
          - id: inwx_acl
            address: [185.181.104.96, 2a0a:c980::53]
            action: transfer
          - id: local_acl
            key: local_key
            action: update
            update-type: DS
        ${let
          toACMEACL = { name, ... }:
            if hasSuffix "_acme.yaml" name
            then
              let
                base = removeSuffix ".yaml" name;
              in indentString "  " ''
                - id: ${base}_acl
                  key: ${base}_key
                  action: update
              ''
            else null;
        in concatStringsSep "\n" (filter (v: v != null) (map toACMEACL knotKeys))}

        mod-rrl:
          - id: default
            rate-limit: 200
            slip: 2

        mod-cookies:
          - id: default
            secret-lifetime: 4h
            badcookie-slip: 1

        submission:
          - id: validating-resolver
            parent: recursive
            check-interval: 5m

        policy:
          - id: rsa2048
            algorithm: rsasha256
            ksk-size: 4096
            zsk-size: 2048
            zsk-lifetime: 30d
            ksk-submission: validating-resolver
          - id: ed25519
            algorithm: ed25519
            nsec3: on
            nsec3-iterations: 0
            ksk-lifetime: 360d
            signing-threads: 2
            ksk-submission: validating-resolver
          - id: ed25519_local-push
            algorithm: ed25519
            nsec3: on
            nsec3-iterations: 0
            ksk-lifetime: 360d
            signing-threads: 2
            ksk-submission: validating-resolver
            cds-cdnskey-publish: double-ds
            propagation-delay: 0
            ds-push: [local]

        template:
          - id: default
            global-module: [mod-cookies/default, mod-rrl/default]
          - id: inwx_zone
            storage: /var/lib/knot
            zonefile-sync: -1
            zonefile-load: difference-no-serial
            serial-policy: dateserial
            journal-content: all
            semantic-checks: on
            dnssec-signing: on
            dnssec-policy: ed25519
            notify: [inwx_notify]
            acl: [inwx_acl]
          - id: acme_zone
            storage: /var/lib/knot
            zonefile-sync: -1
            zonefile-load: difference-no-serial
            serial-policy: dateserial
            journal-content: all
            semantic-checks: on
            dnssec-signing: on
            dnssec-policy: ed25519_local-push

        zone:
        ${concatMapStringsSep "\n" mkZone [
          { domain = "yggdrasil.li";
            addACLs = { "yggdrasil.li" = ["ymir_acme_acl"]; };
          }
          { domain = "nights.email";
            addACLs = { "nights.email" = ["ymir_acme_acl"]; };
          }
          { domain = "141.li";
            acmeDomains = ["webdav.141.li" "141.li"];
            addACLs = { "141.li" = ["ymir_acme_acl"]; };
          }
          { domain = "kleen.li";
            addACLs = { "kleen.li" = ["ymir_acme_acl"]; };
          }
          { domain = "xmpp.li";
            addACLs = { "xmpp.li" = ["ymir_acme_acl"]; };
          }
          { domain = "synapse.li";
            acmeDomains = ["element.synapse.li" "turn.synapse.li" "synapse.li"];
          }
          { domain = "dirty-haskell.org";
            addACLs = { "dirty-haskell.org" = ["ymir_acme_acl"]; };
          }
          { domain = "praseodym.org";
            addACLs = { "praseodym.org" = ["ymir_acme_acl"]; };
          }
          { domain = "rheperire.org";
            addACLs = { "rheperire.org" = ["ymir_acme_acl"]; };
          }
        ]}
      '';
    };

    sops.secrets = listToAttrs (map ({name, path}: nameValuePair name {
      format = "binary";
      owner = "knot";
      sopsFile = path;
    }) knotKeys);

    fileSystems."/var/lib/unbound" =
      { device = "surtr/local/var-lib-unbound";
        fsType = "zfs";
      };

    systemd.services.unbound.unitConfig.RequiresMountsFor = [ "/var/lib/unbound" ];

    services.unbound = {
      enable = true;
      resolveLocalQueries = false;
      stateDir = "/var/lib/unbound";
      localControlSocketPath = "/run/unbound/unbound.ctl";

      settings = {
        server = {
          interface = ["127.0.0.1@5353" "::1@5353"];
          access-control = ["127.0.0.0/8 allow" "::1/128 allow"];
          root-hints = "${pkgs.dns-root-data}/root.hints";

          num-threads = 12;
          so-reuseport = true;
          msg-cache-slabs = 16;
          rrset-cache-slabs = 16;
          infra-cache-slabs = 16;
          key-cache-slabs = 16;

          rrset-cache-size = "100m";
          msg-cache-size = "50m";
          outgoing-range = 8192;
          num-queries-per-thread = 4096;

          so-rcvbuf = "4m";
          so-sndbuf = "4m";

          # serve-expired = true;
          # serve-expired-ttl = 86400;
          # serve-expired-reply-ttl = 0;

          prefetch = true;
          prefetch-key = true;

          minimal-responses = false;

          extended-statistics = true;

          rrset-roundrobin = true;
          use-caps-for-id = true;
        };
      };
    };
  };
}