{ self, ... }:
{
  perSystem =
    { pkgs, self', ... }:
    {
      checks =
        let
          testDomain = "webnstest.example";
          lastIPPath = "/var/lib/webnsupdate/last-ip.json";

          zoneFile = pkgs.writeText "${testDomain}.zoneinfo" ''
            $TTL 60 ; 1 minute
            $ORIGIN ${testDomain}.
            @         IN SOA    ns1.${testDomain}. admin.${testDomain}. (
                          1            ; serial
                          6h           ; refresh
                          1h           ; retry
                          1w           ; expire
                          1d)          ; negative caching TTL

                      IN  NS    ns1.${testDomain}.
            @         IN  A     127.0.0.1
            ns1       IN  A     127.0.0.1
            nsupdate  IN  A     127.0.0.1
            @         IN  AAAA  ::1
            ns1       IN  AAAA  ::1
            nsupdate  IN  AAAA  ::1
          '';

          bindDynamicZone =
            { config, ... }:
            let
              bindCfg = config.services.bind;
              bindData = bindCfg.directory;
              dynamicZonesDir = "${bindData}/zones";
            in
            {
              services.bind.zones.${testDomain} = {
                master = true;
                file = "${dynamicZonesDir}/${testDomain}";
                extraConfig = ''
                  allow-update { key rndc-key; };
                '';
              };

              systemd.services.bind.preStart = ''
                # shellcheck disable=SC2211,SC1127
                rm -f ${dynamicZonesDir}/* # reset dynamic zones

                # create a dynamic zones dir
                mkdir -m 0755 -p ${dynamicZonesDir}
                # copy dynamic zone's file to the dynamic zones dir
                cp ${zoneFile} ${dynamicZonesDir}/${testDomain}
              '';
            };

          webnsupdate-ipv4-machine =
            { lib, ... }:
            {
              imports = [
                bindDynamicZone
                self.nixosModules.webnsupdate
              ];

              config = {
                environment.systemPackages = [
                  pkgs.dig
                  pkgs.curl
                ];

                services = {
                  bind.enable = true;

                  webnsupdate = {
                    enable = true;
                    bindIp = lib.mkDefault "127.0.0.1";
                    keyFile = "/etc/bind/rndc.key";
                    # test:test (user:password)
                    passwordFile = pkgs.writeText "webnsupdate.pass" "FQoNmuU1BKfg8qsU96F6bK5ykp2b0SLe3ZpB3nbtfZA";
                    package = self'.packages.webnsupdate;
                    extraArgs = [
                      "-vvv" # debug messages
                      "--ip-source=ConnectInfo"
                    ];
                    records = ''
                      test1.${testDomain}.
                      test2.${testDomain}.
                      test3.${testDomain}.
                    '';
                  };
                };
              };
            };

          webnsupdate-ipv6-machine = {
            imports = [
              webnsupdate-ipv4-machine
            ];

            config.services.webnsupdate.bindIp = "::1";
          };

          webnsupdate-nginx-machine =
            { lib, config, ... }:
            {
              imports = [
                webnsupdate-ipv4-machine
              ];

              config.services = {
                # Use default IP Source
                webnsupdate.extraArgs = lib.mkForce [ "-vvv" ]; # debug messages

                nginx = {
                  enable = true;
                  recommendedProxySettings = true;

                  virtualHosts.webnsupdate.locations."/".proxyPass =
                    "http://${config.services.webnsupdate.bindIp}:${builtins.toString config.services.webnsupdate.bindPort}";
                };
              };
            };

          webnsupdate-ipv4-only-machine = {
            imports = [ webnsupdate-nginx-machine ];
            config.services.webnsupdate.allowedIPVersion = "ipv4-only";
          };

          webnsupdate-ipv6-only-machine = {
            imports = [ webnsupdate-nginx-machine ];
            config.services.webnsupdate.allowedIPVersion = "ipv6-only";
          };

          # "A" for IPv4, "AAAA" for IPv6, "ANY" for any
          testTemplate =
            {
              ipv4 ? false,
              ipv6 ? false,
              nginx ? false,
              exclusive ? false,
            }:
            if exclusive && (ipv4 == ipv6) then
              builtins.throw "exclusive means one of ipv4 or ipv6 must be set, but not both"
            else
              ''
                IPV4: bool = ${if ipv4 then "True" else "False"}
                IPV6: bool = ${if ipv6 then "True" else "False"}
                NGINX: bool = ${if nginx then "True" else "False"}
                EXCLUSIVE: bool = ${if exclusive then "True" else "False"}
                print(f"{IPV4=} {IPV6=} {EXCLUSIVE=}")

                CURL: str = "curl --fail --no-progress-meter --show-error"

                machine.start(allow_reboot=True)
                machine.wait_for_unit("bind.service")
                machine.wait_for_unit("webnsupdate.service")

                STATIC_DOMAINS: list[str] = ["${testDomain}", "ns1.${testDomain}", "nsupdate.${testDomain}"]
                DYNAMIC_DOMAINS: list[str] = ["test1.${testDomain}", "test2.${testDomain}", "test3.${testDomain}"]

                def dig_cmd(domain: str, record: str, ip: str | None) -> str:
                    match_ip = "" if ip is None else f"\\s\\+60\\s\\+IN\\s\\+{record}\\s\\+{ip}$"
                    return f"dig @localhost {record} {domain} +noall +answer | grep '^{domain}.{match_ip}'"

                def curl_cmd(domain: str, identity: str, path: str, query: dict[str, str]) -> str:
                    from urllib.parse import urlencode
                    q= f"?{urlencode(query)}" if query else ""
                    return f"{CURL} -u {identity} -X GET 'http://{domain}{"" if NGINX else ":5353"}/{path}{q}'"

                def domain_available(domain: str, record: str, ip: str | None=None):
                    machine.succeed(dig_cmd(domain, record, ip))

                def domain_missing(domain: str, record: str, ip: str | None=None):
                    machine.fail(dig_cmd(domain, record, ip))

                def update_records(domain: str="localhost", /, *, path: str="update", **kwargs):
                    machine.succeed(curl_cmd(domain, "test:test", path, kwargs))
                    machine.succeed("cat ${lastIPPath}")

                def update_records_fail(domain: str="localhost", /, *, identity: str="test:test", path: str="update", **kwargs):
                    machine.fail(curl_cmd(domain, identity, path, kwargs))
                    machine.fail("cat ${lastIPPath}")

                def invalid_update(domain: str="localhost"):
                    update_records_fail(domain, identity="bad_user:test")
                    update_records_fail(domain, identity="test:bad_pass")

                # Tests

                with subtest("static DNS records are available"):
                    print(f"{IPV4=} {IPV6=} {EXCLUSIVE=}")
                    for domain in STATIC_DOMAINS:
                        domain_available(domain, "A", "127.0.0.1") # IPv4
                        domain_available(domain, "AAAA", "::1")    # IPv6

                with subtest("dynamic DNS records are missing"):
                    print(f"{IPV4=} {IPV6=} {EXCLUSIVE=}")
                    for domain in DYNAMIC_DOMAINS:
                        domain_missing(domain, "A")    # IPv4
                        domain_missing(domain, "AAAA") # IPv6

                with subtest("invalid auth fails to update records"):
                    print(f"{IPV4=} {IPV6=} {EXCLUSIVE=}")
                    invalid_update()
                    for domain in DYNAMIC_DOMAINS:
                        domain_missing(domain, "A")    # IPv4
                        domain_missing(domain, "AAAA") # IPv6

                if EXCLUSIVE:
                    with subtest("exclusive IP version fails to update with invalid version"):
                        print(f"{IPV4=} {IPV6=} {EXCLUSIVE=}")
                        if IPV6:
                            update_records_fail("127.0.0.1")
                        if IPV4:
                            update_records_fail("[::1]")

                with subtest("valid auth updates records"):
                    print(f"{IPV4=} {IPV6=} {EXCLUSIVE=}")
                    if IPV4:
                        update_records("127.0.0.1")
                    if IPV6:
                        update_records("[::1]")

                    for domain in DYNAMIC_DOMAINS:
                        if IPV4:
                            domain_available(domain, "A", "127.0.0.1")
                        elif IPV6 and EXCLUSIVE:
                            domain_missing(domain, "A")

                        if IPV6:
                            domain_available(domain, "AAAA", "::1")
                        elif IPV4 and EXCLUSIVE:
                            domain_missing(domain, "AAAA")

                with subtest("valid auth fritzbox compatible updates records"):
                    print(f"{IPV4=} {IPV6=} {EXCLUSIVE=}")
                    if IPV4 and IPV6:
                        update_records("127.0.0.1", domain="test", ipv4="1.2.3.4", ipv6="::1234")
                    elif IPV4:
                        update_records("127.0.0.1", ipv4="1.2.3.4", ipv6="")
                    elif IPV6:
                        update_records("[::1]",     ipv4="",        ipv6="::1234")

                    for domain in DYNAMIC_DOMAINS:
                        if IPV4:
                            domain_available(domain, "A", "1.2.3.4")
                        elif IPV6 and EXCLUSIVE:
                            domain_missing(domain, "A")

                        if IPV6:
                            domain_available(domain, "AAAA", "::1234")
                        elif IPV4 and EXCLUSIVE:
                            domain_missing(domain, "AAAA")

                with subtest("valid auth replaces records"):
                    print(f"{IPV4=} {IPV6=} {EXCLUSIVE=}")
                    if IPV4:
                        update_records("127.0.0.1")
                    if IPV6:
                        update_records("[::1]")

                    for domain in DYNAMIC_DOMAINS:
                        if IPV4:
                            domain_available(domain, "A", "127.0.0.1")
                        elif IPV6 and EXCLUSIVE:
                            domain_missing(domain, "A")

                        if IPV6:
                            domain_available(domain, "AAAA", "::1")
                        elif IPV4 and EXCLUSIVE:
                            domain_missing(domain, "AAAA")

                machine.reboot()
                machine.succeed("cat ${lastIPPath}")
                machine.wait_for_unit("webnsupdate.service")
                machine.succeed("cat ${lastIPPath}")

                with subtest("static DNS records are available after reboot"):
                    print(f"{IPV4=} {IPV6=} {EXCLUSIVE=}")
                    for domain in STATIC_DOMAINS:
                        domain_available(domain, "A", "127.0.0.1") # IPv4
                        domain_available(domain, "AAAA", "::1")    # IPv6

                with subtest("dynamic DNS records are available after reboot"):
                    print(f"{IPV4=} {IPV6=} {EXCLUSIVE=}")
                    for domain in DYNAMIC_DOMAINS:
                        if IPV4:
                            domain_available(domain, "A", "127.0.0.1")
                        elif IPV6 and EXCLUSIVE:
                            domain_missing(domain, "A")

                        if IPV6:
                            domain_available(domain, "AAAA", "::1")
                        elif IPV4 and EXCLUSIVE:
                            domain_missing(domain, "AAAA")
              '';
        in
        {
          module-ipv4-test = pkgs.testers.nixosTest {
            name = "webnsupdate-ipv4-module";
            nodes.machine = webnsupdate-ipv4-machine;
            testScript = testTemplate { ipv4 = true; };
          };
          module-ipv6-test = pkgs.testers.nixosTest {
            name = "webnsupdate-ipv6-module";
            nodes.machine = webnsupdate-ipv6-machine;
            testScript = testTemplate { ipv6 = true; };
          };
          module-nginx-test = pkgs.testers.nixosTest {
            name = "webnsupdate-nginx-module";
            nodes.machine = webnsupdate-nginx-machine;
            testScript = testTemplate {
              ipv4 = true;
              ipv6 = true;
              nginx = true;
            };
          };
          module-ipv4-only-test = pkgs.testers.nixosTest {
            name = "webnsupdate-ipv4-only-module";
            nodes.machine = webnsupdate-ipv4-only-machine;
            testScript = testTemplate {
              ipv4 = true;
              nginx = true;
              exclusive = true;
            };
          };
          module-ipv6-only-test = pkgs.testers.nixosTest {
            name = "webnsupdate-ipv6-only-module";
            nodes.machine = webnsupdate-ipv6-only-machine;
            testScript = testTemplate {
              ipv6 = true;
              nginx = true;
              exclusive = true;
            };
          };
        };
    };
}