## This file is a basic test of Mastodon functionalities.
##
## NOTE: This test will fail for Mastodon < 4.3 because of
## https://github.com/mastodon/mastodon/issues/31145

{ pkgs, self }:

let
  lib = pkgs.lib;

  ## FIXME: this binding was not used, but maybe we want a side-effect or something?
  # rebuildableTest = import ./rebuildableTest.nix pkgs;

  testImage = pkgs.copyPathToStore ./green.png;
  testImageColour = "#00FF00";

  seleniumScript =
    pkgs.writers.writePython3Bin "selenium-script"
      { libraries = with pkgs.python3Packages; [ selenium ]; }
      ''
        from selenium import webdriver
        from selenium.webdriver.common.by import By
        from selenium.webdriver.firefox.options import Options
        from selenium.webdriver.support.ui import WebDriverWait

        options = Options()
        options.add_argument("--headless")
        # devtools don't show up in headless screenshots
        # options.add_argument("-devtools")
        service = webdriver.FirefoxService(executable_path="${lib.getExe pkgs.geckodriver}")  # noqa: E501

        driver = webdriver.Firefox(options=options, service=service)
        driver.get("http://mastodon.localhost/public/local")

        # wait until the statuses load
        WebDriverWait(driver, 90).until(
            lambda x: x.find_element(By.CLASS_NAME, "status"))

        driver.save_screenshot("/mastodon-screenshot.png")

        driver.close()
      '';
in

pkgs.nixosTest {
  name = "mastodon";

  nodes = {
    server =
      { config, ... }:
      {
        virtualisation.memorySize = lib.mkVMOverride 4096;
        imports = with self.nixosModules; [
          fediversity
          ../vm/garage-vm.nix
          ../vm/mastodon-vm.nix
          ../vm/interactive-vm.nix
        ];
        # TODO: pair down
        environment.systemPackages = with pkgs; [
          python3
          firefox-unwrapped
          geckodriver
          toot
          xh
          seleniumScript
          helix
          imagemagick
          expect
        ];
        environment.variables = {
          AWS_ACCESS_KEY_ID = config.services.garage.ensureKeys.mastodon.id;
          AWS_SECRET_ACCESS_KEY = config.services.garage.ensureKeys.mastodon.secret;
        };
      };
  };

  testScript =
    { nodes, ... }:
    ''
      import re
      import time

      server.start()

      with subtest("Mastodon starts"):
        server.wait_for_unit("mastodon-web.service")

      # make sure mastodon is fully up and running before we interact with it
      # TODO: is there a way to test for this?
      time.sleep(180)

      with subtest("Account creation"):
        account_creation_output = server.succeed("mastodon-tootctl accounts create test --email test@test.com --confirmed --approve")
        password_match = re.match('.*New password: ([^\n]*).*', account_creation_output, re.S)
        if password_match is None:
          raise Exception(f"account creation did not generate a password.\n{account_creation_output}")
        password = password_match.group(1)
        # print(f"Test user (test@test.com)'s password is: {password}")

      with subtest("Log in with toot"):
        # toot doesn't provide a way to just specify our login details as
        # arguments, so we have to pretend we're typing them in at the prompt;
        # we use 'expect' for this purpose.
        server.succeed(f"""
          expect -c '
            spawn toot login_cli --instance http://mastodon.localhost:55001 --email test@test.com
            expect "Password: "
            send "{password}\\n"
            interact
          ' >&2
        """)

      with subtest("Post a text"):
        server.succeed("echo 'hello mastodon' | toot post")

      with subtest("Post an image"):
        server.succeed("toot post --media ${testImage}")

      with subtest("Access garage"):
        server.succeed("mc alias set garage ${nodes.server.fediversity.internal.garage.api.url} --api s3v4 --path off $AWS_ACCESS_KEY_ID $AWS_SECRET_ACCESS_KEY")
        server.succeed("mc ls garage/mastodon")

      with subtest("Access image in garage"):
        image = server.succeed("mc find garage --regex original")
        image = image.rstrip()
        if image == "":
          raise Exception("image posted to mastodon did not get stored in garage")
        server.succeed(f"mc cat {image} >/garage-image.webp")
        garage_image_hash = server.succeed("identify -quiet -format '%#' /garage-image.webp")
        image_hash = server.succeed("identify -quiet -format '%#' ${testImage}")
        if garage_image_hash != image_hash:
          raise Exception("image stored in garage did not match image uploaded")

      with subtest("Content-Security-Policy allows garage content"):
        headers = server.succeed("xh -h http://mastodon.localhost:55001/public/local")
        csp_match = None
        # I can't figure out re.MULTILINE
        for header in headers.split("\n"):
          csp_match = re.match('^Content-Security-Policy: (.*)$', header)
          if csp_match is not None:
            break
        if csp_match is None:
          raise Exception("mastodon did not send a content security policy header")
        csp = csp_match.group(1)
        # the connect-src content security policy should include the garage server
        ## TODO: use `nodes.server.fediversity.internal.garage.api.url` same as above, but beware of escaping the regex. Be careful with port 80 though.
        garage_csp = re.match(".*; img-src[^;]*web\.garage\.localhost.*", csp)
        if garage_csp is None:
          raise Exception("Mastodon's Content-Security-Policy does not include Garage.")

      # this could in theory give a false positive if mastodon changes it's colorscheme to include ${testImageColour}.
      with subtest("Image displays"):
        server.succeed("selenium-script")
        server.copy_from_vm("/mastodon-screenshot.png", "")
        displayed_colors = server.succeed("convert /mastodon-screenshot.png -define histogram:unique-colors=true -format %c histogram:info:")
        # check that the image displayed somewhere
        image_check = re.match(".*${testImageColour}.*", displayed_colors, re.S)
        if image_check is None:
          raise Exception("cannot detect the uploaded image on mastodon page.")
    '';
}
