{
  pkgs,
  config,
  flake-inputs,
  ...
}:
let
  domain = "ntfy.${config.services.nginx.domain}";
in
{
  imports = [ ./downstream-module.nix ];

  networking.firewall.allowedTCPPorts = [
    80
    443
  ];

  services.ntfy-sh = {
    enable = true;
    package = flake-inputs.nixpkgs-unstable.legacyPackages.${pkgs.system}.ntfy-sh;

    environmentFile = config.sops.secrets."ntfy/users".path;

    settings = {
      base-url = "https://${domain}";
      listen-http = "127.0.0.1:2586";
      behind-proxy = true;

      # Paths
      attachment-cache-dir = "/var/lib/ntfy-sh/attachments";
      cache-file = "/var/lib/ntfy-sh/cache-file.db";
      auth-file = "/var/lib/ntfy-sh/user.db";
      auth-default-access = "deny-all";
      auth-access = [ "*:local-*:wo" ];

      # Don't want to host the front-end
      web-root = "disable";
    };
  };

  services.nginx.virtualHosts."ntfy.${config.services.nginx.domain}" = {
    forceSSL = true;
    useACMEHost = "tlater.net";
    enableHSTS = true;

    locations."/" = {
      proxyWebsockets = true;
      proxyPass = "http://${config.services.ntfy-sh.settings.listen-http}";
      extraConfig = ''
        client_max_body_size 0; # Stream request body to backend
      '';
    };

    # Don't allow writing to topics with the local prefix, *including*
    # webhook writes, since they are set to write-only access from
    # anyone.
    locations."/local" = {
      proxyWebsockets = true;
      proxyPass = "http://${config.services.ntfy-sh.settings.listen-http}";
      extraConfig = ''
        client_max_body_size 0; # Stream request body to backend

        limit_except GET OPTIONS {
          deny all;
        }

        location ~ /trigger$ {
          deny all;
        }
      '';
    };
  };

  sops.secrets."ntfy/users" = { };

  serviceTests = {
    testNtfyConfig = pkgs.testers.runNixOSTest {
      name = "test-ntfy-config";

      node.specialArgs = { inherit flake-inputs; };
      nodes = {
        testHost =
          { lib, ... }:
          {
            imports = [
              ./.
              ../../nginx
              ../../../modules/serviceTests/mocks.nix
            ];

            services.nginx.domain = "testHost";

            # Don't care for testing SSL here
            services.nginx.virtualHosts."ntfy.testHost" = {
              forceSSL = lib.mkForce false;
              enableHSTS = lib.mkForce false;
            };
          };

        client =
          { pkgs, ... }:
          {
            environment.systemPackages = [ pkgs.curl ];
            networking.hosts."192.168.1.2" = [ "ntfy.testHost" ];
          };
      };

      testScript = ''
        import json
        import time
        from contextlib import contextmanager

        def read_client_messages():
            client.wait_for_unit("messages.service")
            messages = [json.loads(line) for line in client.succeed("cat messages").split()]
            client.succeed("systemctl stop messages.service")
            client.succeed("rm messages")

            print(messages)

            return messages

        @contextmanager
        def client_subscribe(topic: str, timeout: int = 2):
            systemd_invocation = [
                "systemd-run",
                "--unit messages",
                "--property=Type=oneshot",
                "--property=SuccessExitStatus=28",
                "--remain-after-exit",
                "--setenv=PATH=$PATH",
                "--same-dir",
                "--no-block",
                "/bin/sh -c"
            ]

            curl = [
                "curl",
                "--silent",
                "--show-error",
                f"--max-time {timeout}",
                "-u tlater:insecure",
                f"http://ntfy.testHost/{topic}/json",
                "> messages"
            ]

            client.succeed(f'{" ".join(systemd_invocation)} "{" ".join(curl)}"')

            # Give some slack so the host doesn't send messages before
            # we're listening
            time.sleep(1)

            yield


        start_all()
        testHost.wait_for_unit("ntfy-sh.service")
        client.wait_until_succeeds("curl http://ntfy.testHost")

        with subtest("subscribing and writing to local topics works"):
            with client_subscribe("local-test"):
                testHost.succeed("curl --fail --silent --show-error --data test http://127.0.0.1:2586/local-test")

            messages = read_client_messages()
            t.assertEqual(len(messages), 2)
            t.assertEqual(messages[1].get("message"), "test")

        with subtest("writing to non-local topics without auth fails"):
            testHost.fail("curl --fail --silent --show-error --data test http://127.0.0.1:2586/test")

        with subtest("writing to *any* topics from outside localhost fails"):
            client.fail("curl --fail --silent --show-error --data test http://ntfy.testHost/test")
            client.fail("curl --fail --silent --show-error --data test http://ntfy.testHost/local-test")
            # GET requests work by default because websocket shenanigans
            client.fail("curl --fail --silent --show-error http://ntfy.testHost/local-test/trigger?message=test")

        with subtest("authenticated messaging works from outside localhost"):
            with client_subscribe("test", 10):
                client.succeed("curl -u tlater:insecure --fail --silent --show-error --data test http://ntfy.testHost/test")
                client.succeed("curl -u tlater:insecure --fail --silent --show-error http://ntfy.testHost/test/trigger?message=test2")

            messages = read_client_messages()
            t.assertEqual(len(messages), 3)
            t.assertEqual(messages[1].get("message"), "test")
            t.assertEqual(messages[2].get("message"), "test2")
      '';
    };
  };
}
