{
  config,
  pkgs,
  ...
}:
#
# xmpp chat service
#
let
  rootDomain = config.networking.domain; # simatime.com
  ssl = {
    cert = "/var/lib/acme/${rootDomain}/fullchain.pem";
    key = "/var/lib/acme/${rootDomain}/key.pem";
  };
in {
  networking.firewall.allowedTCPPorts = [
    # https://prosody.im/doc/ports
    5000 # file transfer
    5222 # client connections
    5269 # server-to-server
    5280 # http
    5281 # https
    5347 # external components
    5582 # telnet console
  ];

  services.prosody = {
    enable = true;
    package =
      pkgs.prosody.override {withCommunityModules = ["conversejs"];};

    # this is necessary bc prosody needs access to the acme certs managed in Omni/Cloud/Web.nix, when
    # i learn how to use security.acme better, and use separate certs, then i can fix this group
    group = "nginx";
    admins = ["bsima@${rootDomain}"];
    allowRegistration = true;
    inherit ssl;
    uploadHttp = {
      domain = "upload.${rootDomain}";
      uploadExpireAfter = toString (60 * 60 * 24 * 30); # 30 days, as seconds
    };

    modules = {
      announce = true;
      blocklist = true;
      bookmarks = true;
      bosh = true;
      carbons = true;
      cloud_notify = true;
      csi = true;
      dialback = true;
      disco = true;
      groups = true;
      http_files = false; # https://prosody.im/doc/modules/mod_http_files
      motd = true;
      pep = true;
      ping = true;
      private = true;
      proxy65 = true;
      register = true;
      roster = true;
      server_contact_info = true;
      smacks = true;
      vcard = true;
      watchregistrations = true;
      websocket = true;
      welcome = true;
    };

    extraConfig = ''
      conversejs_options = {
        allow_registration = true;
        bosh_service_url = "https://${rootDomain}/http-bind";
        debug = true;
        loglevel = "debug";
        -- default_domain = "${rootDomain}";
        -- domain_placeholder = "${rootDomain}";
        -- jid = "${rootDomain}";
        -- keepalive = true;
        -- registration_domain = "${rootDomain}";
        websocket_url = "wss://${rootDomain}/xmpp-websocket";
      }

      cross_domain_websocket = { "https://${rootDomain}", "https://anon.${rootDomain}" }
      cross_domain_bosh = false; -- handle this with nginx
      consider_bosh_secure = true;

      -- this is a virtualhost that allows anonymous authentication. use this
      -- for a public lobby. the nix module doesn't support 'authentication'
      -- so i have to do this here.
      VirtualHost "anon.${rootDomain}"
        authentication = "anonymous"
        ssl = {
          cafile = "/etc/ssl/certs/ca-bundle.crt";
          key = "${ssl.key}";
          certificate = "${ssl.cert}";
        };
    '';

    muc = [
      {
        domain = "conference.${rootDomain}";
        maxHistoryMessages = 10000;
        name = "Chat Rooms";
        restrictRoomCreation = "admin";
        roomDefaultHistoryLength = 20;
        roomDefaultMembersOnly = true;
        roomDefaultModerated = true;
        roomDefaultPublic = false;
      }
    ];

    virtualHosts = {
      "${rootDomain}" = {
        domain = "${rootDomain}";
        enabled = true;
        inherit ssl;
      };
    };
  };

  services.prosody-filer = {enable = true;};

  services.nginx.virtualHosts."${rootDomain}".locations = {
    "/http-bind" = {
      proxyPass = "https://${rootDomain}:5281/http-bind";
      extraConfig = ''
        proxy_set_header Host $host;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_buffering off;
        add_header Access-Control-Allow-Origin "*";
      '';
    };

    "/xmpp-websocket" = {
      proxyPass = "https://${rootDomain}:5281/xmpp-websocket";
      extraConfig = ''
        proxy_http_version 1.1;
        proxy_buffering off;
        proxy_set_header Host $host;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";
        proxy_read_timeout 86400;
        add_header Access-Control-Allow-Origin "*";
      '';
    };

    "/chat" = {
      proxyPass = "https://${rootDomain}:5281/conversejs";
      extraConfig = ''
        add_header Access-Control-Allow-Origin "*";
      '';
    };
  };

  services.nginx.virtualHosts."anon.${rootDomain}" = {
    useACMEHost = "${rootDomain}";
    forceSSL = true;
    locations = {
      "/http-bind" = {
        proxyPass = "https://anon.${rootDomain}:5281/http-bind";
        extraConfig = ''
          proxy_set_header Host $host;
          proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
          proxy_set_header X-Forwarded-Proto $scheme;
          proxy_buffering off;
          if ($request_method ~* "(GET|POST)") {
            add_header Access-Control-Allow-Origin "*";
          }
          if ($request_method = OPTIONS) {
            add_header Access-Control-Allow-Origin  "*";
            add_header Access-Control-Allow-Methods "GET, POST, OPTIONS, HEAD";
            add_header Access-Control-Allow-Headers "Authorization, Origin, X-Requested-With, Content-Type, Accept";
            return 200;
          }
        '';
      };
    };
  };

  users.users.nginx.extraGroups = ["prosody"];

  # restart prosody when we renew the main cert
  # https://github.com/NixOS/nixpkgs/issues/133904
  systemd.services.prosody.requires = ["acme-finished-${rootDomain}.target"];
  systemd.services.prosody.after = ["acme-finished-${rootDomain}.target"];
}