{ config, pkgs, ... }:
{
  assertions = [{
    assertion = config.networking.networkmanager.enable;
    message = ''This module is only tested with NetworkManager'';
  } {
    assertion = ! config.services.resolved.enable;
    message = ''This module is incompatible with resolved as resolved does not listen to IPv6 loopback'';
  } {
    assertion = config.networking.enableIPv6;
    message = ''This module assumes that IPv6 networking is available'';
  }];

  # Encrypted, anonymized DNS queries.
  #
  # NixOS networking options:
  #   https://nixos.wiki/wiki/Encrypted_DNS#Setting_nameservers
  # Exmaple dnscrypt-proxy config:
  #   https://github.com/DNSCrypt/dnscrypt-proxy/blob/master/dnscrypt-proxy/example-dnscrypt-proxy.toml
  # NixOS config examples:
  #   https://nixos.wiki/wiki/Encrypted_DNS#dnscrypt-proxy2
  #   https://github.com/LudovicoPiero/dotfiles/blob/338b0585d195e6644df9bf8b63fd574af7c18e26/cells/workstations/nixosProfiles/dnscrypt2/default.nix
  #
  # Check if service is running
  #   systemctl status dnscrypt-proxy2.service
  #
  # Check if it is working
  #   https://wiki.archlinux.org/title/Dnscrypt-proxy#Check_if_dnscrypt-proxy_is_working
  #
  # View generated config file:
  #   cat "$(systemctl show -P FragmentPath dnscrypt-proxy2.service)" | grep 'ExecStart='
  #   cat ....toml

  # Example: Running c in a container and routhing dnscrypt-proxy queries through it
  #   https://github.com/AtaraxiaSjel/nixos-config/blob/3510d178bafeb5d742806d25d5c6c34570c498e8/profiles/workspace/proxy.nix

  # TODO
  #   create new config option
  #     encrypted-dns
  #   If enabled
  #     use dnscrypt
  #     don't use adguard and ffmuc DNS servers
  #     Firefox use system DNS

  networking.nameservers = [
    # IPv4
    "127.0.0.1"
    # IPv6
    "::1"
  ];
  #networking.nameservers = [
  #  # https://www.kuketz-blog.de/empfehlungsecke/#dns
  #  # unfiltered.adguard-dns.com (supports DNSSEC)
  # "94.140.14.140" "94.140.14.141"
  #  # https://www.kuketz-blog.de/empfehlungsecke/#dns
  #  # dot.ffmuc.net (supports DNSSEC)
  # "5.1.66.255" "185.150.99.255"
  #];

  # If using dhcpcd.
  networking.dhcpcd.extraConfig = "nohook resolv.conf";
  # If using NetworkManager.
  networking.networkmanager.dns = "none";

  systemd.services.dnscrypt-proxy2.serviceConfig = {
    StateDirectory = "dnscrypt-proxy";
  };

  services.dnscrypt-proxy2 = {
    enable = true;
    settings = {
      #listen_addresses = ['127.0.0.1:53'];
      # Enable a DNS cache to reduce latency and outgoing traffic
      cache = true;
      # Dont alter the TTL of cached entries
      # https://github.com/DNSCrypt/dnscrypt-proxy/issues/1552#issuecomment-750021306
      cache_min_ttl = 0;
      # DNSCrypt: Create a new, unique key for every single DNS query.
      # This may improve privacy but can also have a significant impact on CPU usage.
      # Only enable if you don't have a lot of network load.
      #dnscrypt_ephemeral_keys = false

      # The cipher suite can't be changed for TLS 1.3 connections, see
      #   https://github.com/dnscrypt/dnscrypt-proxy/wiki/Performance#cipher-suites-doh
      #   https://github.com/DNSCrypt/dnscrypt-proxy/issues/2359#issuecomment-1488501839
      #tls_cipher_suite = ...

      bootstrap_resolvers = [
        #
        # Local DNS servers
        #

        # DNS server of Fritz!Box guest WiFi
        "192.168.179.1:53"

        #
        # Public DNS servers
        #

        # https://www.kuketz-blog.de/empfehlungsecke/#dns
        # dot.ffmuc.net (supports DNSSEC)
       "5.1.66.255:53" "185.150.99.255:53"
        # https://www.kuketz-blog.de/empfehlungsecke/#dns
        # unfiltered.adguard-dns.com (supports DNSSEC)
       "94.140.14.140:853" "94.140.14.141:853"

        "9.9.9.11:53" # Quad9
        "1.1.1.1:53" # Cloudflare
        "8.8.8.8:53" # Google

      ];

      # Use servers reachable over IPv4.
      ipv4_servers = true;
      # Use servers reachable over IPv6.
      # Do not enable if you don't have IPv6 connectivity.
      ipv6_servers = true;
      block_ipv6 = false;
      # Use servers implementing the DNSCrypt protocol.
      dnscrypt_servers = true;
      # Use servers implementing the DNS-over-HTTPS protocol.
      doh_servers = false;
      # Use servers implementing the Oblivious DoH protocol
      odoh_servers = false;
      # Server must support DNS security extensions (DNSSEC).
      require_dnssec = true;
      # Server must not log user queries (declarative).
      require_nolog = true;
      # Server must not enforce its own blacklist (for parental control, ads blocking...).
      require_nofilter = true;

      # Fetch list of dnscrypt server names.
      # These server names are used below in the `server_names` and `via` options.
      sources = {
        public-resolvers = {
          urls = [
            "https://raw.githubusercontent.com/DNSCrypt/dnscrypt-resolvers/master/v3/public-resolvers.md"
          ];
          cache_file = "/var/lib/dnscrypt-proxy2/public-resolvers.md";
          minisign_key = "RWQf6LRCGA9i53mlYecO4IzT51TGPpvWucNSCh1CBM0QTaLn73Y7GFO3";
        };
        relays = {
          urls = [
            "https://raw.githubusercontent.com/DNSCrypt/dnscrypt-resolvers/master/v3/relays.md"
          ];
          cache_file = "/var/lib/dnscrypt-proxy2/relays.md";
          minisign_key = "RWQf6LRCGA9i53mlYecO4IzT51TGPpvWucNSCh1CBM0QTaLn73Y7GFO3";
        };
      };

      # Names of servers to be used for DNS queries.
      # These are not contacted directly.
      # Instead, the queries are forwarded by relay servers.
      server_names = [
        # Operated by https://cryptostorm.is/
        "cs-berlin"
        "cs-de"
        "cs-dus1"
        "cs-dus2"
        "cs-dus3"
        "cs-dus4"
        # Other operators.
        "dns.digitale-gesellschaft.ch"
        "dns.digitale-gesellschaft.ch-ipv6"
        "dct-de"
        "dct-nl"
        "dns.digitalsize.net"
        "dns.digitalsize.net-ipv6"
        "ibksturm" # Switzerland
        "doh-ibksturm" # Switzerland
        "faelix-ch-ipv4" # Switzerland

        # Already used as relay server.
        #"dnswarden-uncensor-dc-swiss" # Switzerland

        # DNS servers that filter some requests.
        #"switch" # Filters requests
        #"dnsforge.de" # Filters requests
        #"brahma-world" # Filters requests
        #"brahma-world-ipv6" # Filters requests
        #"libredns" # No DNSSEC
      ];
      anonymized_dns = {
        routes = [
          {
            # All DNS servers given in `server_names`.
            server_name = "*";
            # Relay servers.
            # These forward requests to the chosen DNS servers.
            via = [
              "anon-digitalprivacy.diy-ipv4" # Germany, by https://digitalprivacy.diy
              "anon-serbica" # Netherlands, by https://litepay.ch
              "anon-kama" # France, by Frank Denis (@jedisct1)
              "anon-scaleway" # France, by Frank Denis (@jedisct1)
              "anon-scaleway2" # France, by Frank Denis (@jedisct1)
              "anon-fluffycat-fr-01" # France
              "anon-dnswarden-swiss"

              # Already used as DNS server.
              #"anon-cs-berlin"
              #"anon-cs-de"
              #"anon-cs-dus1" # Germany
              #"anon-cs-dus2" # Germany
              #"anon-cs-dus3" # Germany
              #"anon-cs-dus4" # Germany
              #"anon-cs-fr"
              #"anon-cs-dk"
              #"anon-cs-belgium"
              #"anon-cs-nl"
              #"anon-cs-nl2"
              #"anon-cs-poland"
              #"anon-cs-czech"
              #"anon-cs-austria"
            ];
          }
        ];
        # Skip resolvers incompatible with anonymization instead of using them directly.
        skip_incompatible = true;
      };

    # As this dict is converted to JSON, we can't use `proxy = lib.mkIf (...) "socks5://127.0.0.1:9050"` inside it - it won't be evaluated.
    # Instead, we merge it with another dict below:
    } // (
        # On some networks dnscrypt-proxy can't resove DNS queries.
        #
        # Example: Fritz!Box Guest WiFi
        #   https://docs.pi-hole.net/routers/fritzbox/
        #   The Fritz!Box always sets its own IP as DNS server for the guest network.
        #
        # Solution: Proxy dnscrypt-proxy through Tor
        # - Currently, we have this enabled.
        # - The latency of DNS queries is higher than without Tor - at about 130ms.
       if config.services.tor.torsocks.enable
       then {
         # Route all TCP connections to a local Tor node.
         # As Tor doesn't support  UDP, `force_tcp` has to be set to `true`.
         proxy = "socks5://127.0.0.1:9050";
         # This can be useful if you need to route everything through Tor.
         # Otherwise, leave this to `false`.
         force_tcp = true;
       } else {}
     );
  };
}