{ hostPkgs, lib, ... }:

let
  movim = {
    domain = "movim.local";
    port = 8080;
    info = "No ToS in tests";
    description = "NixOS testing server";
  };
  ejabberd = {
    domain = "ejabberd.local";
    ports = {
      c2s = 5222;
      s2s = 5269;
      http = 5280;
    };
    spoolDir = "/var/lib/ejabberd";
    admin = rec {
      JID = "${username}@${ejabberd.domain}";
      username = "romeo";
      password = "juliet";
    };
  };

  # START OF EJABBERD CONFIG ##################################################
  #
  # Ejabberd has sparse defaults as it is a generic XMPP server. As such this
  # config might be longer than expected for a test.
  #
  # Movim suggests: https://github.com/movim/movim/wiki/Configure ejabberd
  #
  # In the future this may be the default setup
  # See: https://github.com/NixOS/nixpkgs/pull/312316
  ejabberd_config_file =
    let
      settingsFormat = hostPkgs.formats.yaml { };
    in
    settingsFormat.generate "ejabberd.yml" {
      loglevel = "info";
      hide_sensitive_log_data = false;
      hosts = [ ejabberd.domain ];
      default_db = "mnesia";
      acme.auto = false;
      s2s_access = "s2s";
      s2s_use_starttls = false;
      new_sql_schema = true;
      acl = {
        admin = [
          { user = ejabberd.admin.JID; }
        ];
        local.user_regexp = "";
        loopback.ip = [
          "127.0.0.1/8"
          "::1/128"
        ];
      };
      access_rules = {
        c2s = {
          deny = "blocked";
          allow = "all";
        };
        s2s = {
          allow = "all";
        };
        local.allow = "local";
        announce.allow = "admin";
        configure.allow = "admin";
        pubsub_createnode.allow = "local";
        trusted_network.allow = "loopback";
      };
      api_permissions = {
        "console commands" = {
          from = [ "ejabberd_ctl" ];
          who = "all";
          what = "*";
        };
      };
      shaper = {
        normal = {
          rate = 3000;
          burst_size = 20000;
        };
        fast = 100000;
      };
      modules = {
        mod_caps = { };
        mod_disco = { };
        mod_mam = { };
        mod_http_upload = {
          docroot = "${ejabberd.spoolDir}/uploads";
          dir_mode = "0755";
          file_mode = "0644";
          get_url = "http://@HOST@/upload";
          put_url = "http://@HOST@/upload";
          max_size = 65536;
          custom_headers = {
            Access-Control-Allow-Origin = "http://@HOST@,http://${movim.domain}";
            Access-Control-Allow-Methods = "GET,HEAD,PUT,OPTIONS";
            Access-Control-Allow-Headers = "Content-Type";
          };
        };
        # This PubSub block is required for Movim to work.
        #
        # See: https://github.com/movim/movim/wiki/Configure ejabberd#pubsub
        mod_pubsub = {
          hosts = [ "pubsub.@HOST@" ];
          access_createnode = "pubsub_createnode";
          ignore_pep_from_offline = false;
          last_item_cache = false;
          max_items_node = 2048;
          default_node_config = {
            max_items = 2048;
          };
          plugins = [
            "flat"
            "pep"
          ];
          force_node_config = {
            "storage:bookmarks".access_model = "whitelist";
            "eu.siacs.conversations.axolotl.*".access_model = "open";
            "urn:xmpp:bookmarks:0" = {
              access_model = "whitelist";
              send_last_published_item = "never";
              max_items = "infinity";
              persist_items = true;
            };
            "urn:xmpp:bookmarks:1" = {
              access_model = "whitelist";
              send_last_published_item = "never";
              max_items = "infinity";
              persist_items = true;
            };
            "urn:xmpp:pubsub:movim-public-subscription" = {
              access_model = "whitelist";
              max_items = "infinity";
              persist_items = true;
            };
            "urn:xmpp:microblog:0" = {
              notify_retract = true;
              max_items = "infinity";
              persist_items = true;
            };
            "urn:xmpp:microblog:0:comments*" = {
              access_model = "open";
              notify_retract = true;
              max_items = "infinity";
              persist_items = true;
            };
          };
        };
        mod_stream_mgmt = { };
      };
      listen = [
        {
          module = "ejabberd_c2s";
          port = ejabberd.ports.c2s;
          max_stanza_size = 262144;
          access = "c2s";
          starttls_required = false;
        }
        {
          module = "ejabberd_s2s_in";
          port = ejabberd.ports.s2s;
          max_stanza_size = 524288;
          shaper = "fast";
        }
        {
          module = "ejabberd_http";
          port = ejabberd.ports.http;
          request_handlers = {
            "/upload" = "mod_http_upload";
          };
        }
      ];
    };
  # END OF EJABBERD CONFIG ##################################################
in
{
  name = "movim-ejabberd-h2o";

  meta = {
    maintainers = with lib.maintainers; [ toastal ];
  };

  nodes = {
    server =
      { pkgs, ... }:
      {
        environment.systemPackages = [
          # For testing
          pkgs.websocat
        ];

        services.movim = {
          inherit (movim) domain port;
          enable = true;
          verbose = true;
          podConfig = {
            inherit (movim) description info;
            xmppdomain = ejabberd.domain;
          };
          database = {
            type = "postgresql";
            createLocally = true;
          };
          h2o = { };
        };

        services.ejabberd = {
          inherit (ejabberd) spoolDir;
          enable = true;
          configFile = ejabberd_config_file;
          imagemagick = false;
        };

        services.h2o.settings = {
          compress = "ON";
        };

        systemd.services.ejabberd = {
          serviceConfig = {
            # Certain misconfigurations can cause RAM usage to swell before
            # crashing; fail sooner with more-than-liberal memory limits
            StartupMemoryMax = "1G";
            MemoryMax = "512M";
          };
        };

        networking = {
          firewall.allowedTCPPorts = with ejabberd.ports; [
            c2s
            s2s
          ];
          extraHosts = ''
            127.0.0.1 ${movim.domain}
            127.0.0.1 ${ejabberd.domain}
          '';
        };
      };
  };

  testScript = # python
    ''
      ejabberdctl = "su ejabberd -s $(which ejabberdctl) "

      server.wait_for_unit("phpfpm-movim.service")
      server.wait_for_unit("h2o.service")
      server.wait_for_open_port(${toString movim.port})
      server.wait_for_open_port(80)

      server.wait_for_unit("ejabberd.service")
      ejabberd_status = server.succeed(ejabberdctl + "status")
      assert "status: started" in ejabberd_status
      server.succeed(ejabberdctl + "register ${ejabberd.admin.username} ${ejabberd.domain} ${ejabberd.admin.password}")

      server.wait_for_unit("movim.service")

      # Test unauthenticated
      server.fail("curl -L --fail-with-body --max-redirs 0 http://${movim.domain}/chat")

      # Test basic Websocket
      server.succeed("echo | websocat --origin 'http://${movim.domain}' 'ws://${movim.domain}/ws/?path=login&offset=0'")

      # Test login + create cookiejar
      login_html = server.succeed("curl --fail-with-body -c /tmp/cookies http://${movim.domain}/login")
      assert "${movim.description}" in login_html
      assert "${movim.info}" in login_html

      # Test authentication POST
      server.succeed("curl --fail-with-body -b /tmp/cookies -X POST --data-urlencode 'username=${ejabberd.admin.JID}' --data-urlencode 'password=${ejabberd.admin.password}' http://${movim.domain}/login")

      server.succeed("curl -L --fail-with-body --max-redirs 1 -b /tmp/cookies http://${movim.domain}/chat")
    '';
}
