import ../make-test-python.nix ({ pkgs, ... }:
  let
    homeserverUrl = "http://homeserver:8008";
  in
  {
    name = "matrix-appservice-irc";
    meta = {
      maintainers = pkgs.matrix-appservice-irc.meta.maintainers;
    };

    nodes = {
      homeserver = { pkgs, ... }: {
        # We'll switch to this once the config is copied into place
        specialisation.running.configuration = {
          services.matrix-synapse = {
            enable = true;
            settings = {
              database.name = "sqlite3";
              app_service_config_files = [ "/registration.yml" ];

              enable_registration = true;

              # don't use this in production, always use some form of verification
              enable_registration_without_verification = true;

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

          networking.firewall.allowedTCPPorts = [ 8008 ];
        };
      };

      ircd = { pkgs, ... }: {
        services.ngircd = {
          enable = true;
          config = ''
            [Global]
              Name = ircd.ircd
              Info = Server Info Text
              AdminInfo1 = _

            [Channel]
              Name = #test
              Topic = a cool place

            [Options]
              PAM = no
          '';
        };
        networking.firewall.allowedTCPPorts = [ 6667 ];
      };

      appservice = { pkgs, ... }: {
        services.matrix-appservice-irc = {
          enable = true;
          registrationUrl = "http://appservice:8009";

          settings = {
            homeserver.url = homeserverUrl;
            homeserver.domain = "homeserver";

            ircService.servers."ircd" = {
              name = "IRCd";
              port = 6667;
              dynamicChannels = {
                enabled = true;
                aliasTemplate = "#irc_$CHANNEL";
              };
            };
          };
        };

        networking.firewall.allowedTCPPorts = [ 8009 ];
      };

      client = { pkgs, ... }: {
        environment.systemPackages = [
          (pkgs.writers.writePython3Bin "do_test"
          {
            libraries = [ pkgs.python3Packages.matrix-nio ];
            flakeIgnore = [
              # We don't live in the dark ages anymore.
              # Languages like Python that are whitespace heavy will overrun
              # 79 characters..
              "E501"
            ];
          } ''
              import sys
              import socket
              import functools
              from time import sleep
              import asyncio

              from nio import AsyncClient, RoomMessageText, JoinResponse


              async def matrix_room_message_text_callback(matrix: AsyncClient, msg: str, _r, e):
                  print("Received matrix text message: ", e)
                  if msg in e.body:
                      print("Received hi from IRC")
                      await matrix.close()
                      exit(0)  # Actual exit point


              class IRC:
                  def __init__(self):
                      sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
                      sock.connect(("ircd", 6667))
                      sock.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1)
                      sock.send(b"USER bob bob bob :bob\n")
                      sock.send(b"NICK bob\n")
                      self.sock = sock

                  def join(self, room: str):
                      self.sock.send(f"JOIN {room}\n".encode())

                  def privmsg(self, room: str, msg: str):
                      self.sock.send(f"PRIVMSG {room} :{msg}\n".encode())

                  def expect_msg(self, body: str):
                      buffer = ""
                      while True:
                          buf = self.sock.recv(1024).decode()
                          buffer += buf
                          if body in buffer:
                              return


              async def run(homeserver: str):
                  irc = IRC()

                  matrix = AsyncClient(homeserver)
                  response = await matrix.register("alice", "foobar")
                  print("Matrix register response: ", response)

                  response = await matrix.join("#irc_#test:homeserver")
                  print("Matrix join room response:", response)
                  assert isinstance(response, JoinResponse)
                  room_id = response.room_id

                  irc.join("#test")
                  # FIXME: what are we waiting on here? Matrix? IRC? Both?
                  # 10s seem bad for busy hydra machines.
                  sleep(10)

                  # Exchange messages
                  print("Sending text message to matrix room")
                  response = await matrix.room_send(
                      room_id=room_id,
                      message_type="m.room.message",
                      content={"msgtype": "m.text", "body": "hi from matrix"},
                  )
                  print("Matrix room send response: ", response)
                  irc.privmsg("#test", "hi from irc")

                  print("Waiting for the matrix message to appear on the IRC side...")
                  irc.expect_msg("hi from matrix")

                  callback = functools.partial(
                      matrix_room_message_text_callback, matrix, "hi from irc"
                  )
                  matrix.add_event_callback(callback, RoomMessageText)

                  print("Waiting for matrix message...")
                  await matrix.sync_forever()

                  exit(1)  # Unreachable


              if __name__ == "__main__":
                  asyncio.run(run(sys.argv[1]))
            ''
          )
        ];
      };
    };

    testScript = ''
      import pathlib
      import os

      start_all()

      ircd.wait_for_unit("ngircd.service")
      ircd.wait_for_open_port(6667)

      with subtest("start the appservice"):
          appservice.wait_for_unit("matrix-appservice-irc.service")
          appservice.wait_for_open_port(8009)

      with subtest("copy the registration file"):
          appservice.copy_from_vm("/var/lib/matrix-appservice-irc/registration.yml")
          homeserver.copy_from_host(
              str(pathlib.Path(os.environ.get("out", os.getcwd())) / "registration.yml"), "/"
          )
          homeserver.succeed("chmod 444 /registration.yml")

      with subtest("start the homeserver"):
          homeserver.succeed(
              "/run/current-system/specialisation/running/bin/switch-to-configuration test >&2"
          )

          homeserver.wait_for_unit("matrix-synapse.service")
          homeserver.wait_for_open_port(8008)

      with subtest("ensure messages can be exchanged"):
          client.succeed("do_test ${homeserverUrl} >&2")
    '';
  })
