import ../make-test-python.nix (
  { pkgs, ... }:
  let
    # Set up SSL certs for Synapse to be happy.
    runWithOpenSSL = file: cmd: pkgs.runCommand file
      {
        buildInputs = [ pkgs.openssl ];
      }
      cmd;

    ca_key = runWithOpenSSL "ca-key.pem" "openssl genrsa -out $out 2048";
    ca_pem = runWithOpenSSL "ca.pem" ''
      openssl req \
        -x509 -new -nodes -key ${ca_key} \
        -days 10000 -out $out -subj "/CN=snakeoil-ca"
    '';
    key = runWithOpenSSL "matrix_key.pem" "openssl genrsa -out $out 2048";
    csr = runWithOpenSSL "matrix.csr" ''
      openssl req \
         -new -key ${key} \
         -out $out -subj "/CN=localhost" \
    '';
    cert = runWithOpenSSL "matrix_cert.pem" ''
      openssl x509 \
        -req -in ${csr} \
        -CA ${ca_pem} -CAkey ${ca_key} \
        -CAcreateserial -out $out \
        -days 365
    '';
  in
  {
    name = "mjolnir";
    meta = with pkgs.lib; {
      maintainers = teams.matrix.members;
    };

    nodes = {
      homeserver = { pkgs, ... }: {
        services.matrix-synapse = {
          enable = true;
          settings = {
            database.name = "sqlite3";
            tls_certificate_path = "${cert}";
            tls_private_key_path = "${key}";
            enable_registration = true;
            enable_registration_without_verification = true;
            registration_shared_secret = "supersecret-registration";

            listeners = [ {
              # The default but tls=false
              bind_addresses = [
                "0.0.0.0"
              ];
              port = 8448;
              resources = [ {
                compress = true;
                names = [ "client" ];
              } {
                compress = false;
                names = [ "federation" ];
              } ];
              tls = false;
              type = "http";
              x_forwarded = false;
            } ];
          };
        };

        networking.firewall.allowedTCPPorts = [ 8448 ];

        environment.systemPackages = [
          (pkgs.writeShellScriptBin "register_mjolnir_user" ''
            exec ${pkgs.matrix-synapse}/bin/register_new_matrix_user \
              -u mjolnir \
              -p mjolnir-password \
              --admin \
              --shared-secret supersecret-registration \
              http://localhost:8448
          ''
          )
          (pkgs.writeShellScriptBin "register_moderator_user" ''
            exec ${pkgs.matrix-synapse}/bin/register_new_matrix_user \
              -u moderator \
              -p moderator-password \
              --no-admin \
              --shared-secret supersecret-registration \
              http://localhost:8448
          ''
          )
        ];
      };

      mjolnir = { pkgs, ... }: {
        services.mjolnir = {
          enable = true;
          homeserverUrl = "http://homeserver:8448";
          pantalaimon = {
            enable = true;
            username = "mjolnir";
            passwordFile = pkgs.writeText "password.txt" "mjolnir-password";
            # otherwise mjolnir tries to connect to ::1, which is not listened by pantalaimon
            options.listenAddress = "127.0.0.1";
          };
          managementRoom = "#moderators:homeserver";
        };
      };

      client = { pkgs, ... }: {
        environment.systemPackages = [
          (pkgs.writers.writePython3Bin "create_management_room_and_invite_mjolnir"
            { libraries = with pkgs.python3Packages; [
                matrix-nio
              ] ++ matrix-nio.optional-dependencies.e2e;
            } ''
            import asyncio

            from nio import (
                AsyncClient,
                EnableEncryptionBuilder
            )


            async def main() -> None:
                client = AsyncClient("http://homeserver:8448", "moderator")

                await client.login("moderator-password")

                room = await client.room_create(
                    name="Moderators",
                    alias="moderators",
                    initial_state=[EnableEncryptionBuilder().as_dict()],
                )

                await client.join(room.room_id)
                await client.room_invite(room.room_id, "@mjolnir:homeserver")

            asyncio.run(main())
          ''
          )
        ];
      };
    };

    testScript = ''
      with subtest("start homeserver"):
        homeserver.start()

        homeserver.wait_for_unit("matrix-synapse.service")
        homeserver.wait_until_succeeds("curl --fail -L http://localhost:8448/")

      with subtest("register users"):
        # register mjolnir user
        homeserver.succeed("register_mjolnir_user")
        # register moderator user
        homeserver.succeed("register_moderator_user")

      with subtest("start mjolnir"):
        mjolnir.start()

        # wait for pantalaimon to be ready
        mjolnir.wait_for_unit("pantalaimon-mjolnir.service")
        mjolnir.wait_for_unit("mjolnir.service")

        mjolnir.wait_until_succeeds("curl --fail -L http://localhost:8009/")

      with subtest("ensure mjolnir can be invited to the management room"):
        client.start()

        client.wait_until_succeeds("curl --fail -L http://homeserver:8448/")

        client.succeed("create_management_room_and_invite_mjolnir")

        mjolnir.wait_for_console_text("Startup complete. Now monitoring rooms")
    '';
  }
)
