let
  # generate one using openssl (somehow)
  # XXX: when importing, garage tells you importing is only meant for keys previously generated by garage. is it okay to generate them using openssl? it seems to work fine
  snakeoil_key = {
    id = "GK22a15201acacbd51cd43e327";
    secret = "82b2b4cbef27bf8917b350d5b10a87c92fa9c8b13a415aeeea49726cf335d74e";
  };
in

# TODO: expand to a multi-machine setup
{ config, lib, pkgs, ... }:

let
  inherit (builtins) toString;
  inherit (lib) types mkOption mkEnableOption optionalString concatStringsSep;
  inherit (lib.strings) escapeShellArg;
  cfg = config.services.garage;
  fedicfg = config.fediversity.internal.garage;
  concatMapAttrs = scriptFn: attrset: concatStringsSep "\n" (lib.mapAttrsToList scriptFn attrset);
  ensureBucketScriptFn = bucket: { website, aliases, corsRules }:
    let
      bucketArg = escapeShellArg bucket;
      corsRulesJSON = escapeShellArg (builtins.toJSON {
        CORSRules = [{
          AllowedHeaders = corsRules.allowedHeaders;
          AllowedMethods = corsRules.allowedMethods;
          AllowedOrigins = corsRules.allowedOrigins;
        }];
      });
    in ''
      # garage bucket info tells us if the bucket already exists
      garage bucket info ${bucketArg} || garage bucket create ${bucketArg}

      # TODO: should this --deny the website if `website` is false?
      ${optionalString website ''
        garage bucket website --allow ${bucketArg}
      ''}

      ${concatStringsSep "\n" (map (alias: ''
        garage bucket alias ${bucketArg} ${escapeShellArg alias}
      '') aliases)}

      ${optionalString corsRules.enable ''
        garage bucket allow --read --write --owner ${bucketArg} --key tmp
        # TODO: endpoin-url should not be hard-coded
        aws --region ${cfg.settings.s3_api.s3_region} --endpoint-url ${fedicfg.api.url} s3api put-bucket-cors --bucket ${bucketArg} --cors-configuration ${corsRulesJSON}
        garage bucket deny --read --write --owner ${bucketArg} --key tmp
      ''}
    '';
  ensureBucketsScript = concatMapAttrs ensureBucketScriptFn cfg.ensureBuckets;
  ensureAccessScriptFn = key: bucket: { read, write, owner }: ''
    garage bucket allow ${optionalString read "--read"} ${optionalString write "--write"} ${optionalString owner "--owner"} \
      ${escapeShellArg bucket} --key ${escapeShellArg key}
  '';
  ensureKeyScriptFn = key: {id, secret, ensureAccess}: ''
    ## FIXME: Check whether the key exist and skip this step if that is the case. Get rid of this `|| :`
    garage key import --yes -n ${escapeShellArg key} ${escapeShellArg id} ${escapeShellArg secret} || :
    ${concatMapAttrs (ensureAccessScriptFn key) ensureAccess}
  '';
  ensureKeysScript = concatMapAttrs ensureKeyScriptFn cfg.ensureKeys;
in

{
  # add in options to ensure creation of buckets and keys
  options = {
    services.garage = {
      ensureBuckets = mkOption {
        type = types.attrsOf (types.submodule {
          options = {
            website = mkOption {
              type = types.bool;
              default = false;
            };
            # I think setting corsRules should allow another website to show images from your bucket
            corsRules = {
              enable = mkEnableOption "CORS Rules";
              allowedHeaders = mkOption {
                type = types.listOf types.str;
                default = [];
              };
              allowedMethods = mkOption {
                type = types.listOf types.str;
                default = [];
              };
              allowedOrigins = mkOption {
                type = types.listOf types.str;
                default = [];
              };
            };
            aliases = mkOption {
              type = types.listOf types.str;
              default = [];
            };
          };
        });
        default = {};
      };
      ensureKeys = mkOption {
        type = types.attrsOf (types.submodule {
          # TODO: these should be managed as secrets, not in the nix store
          options = {
            id = mkOption {
              type = types.str;
            };
            secret = mkOption {
              type = types.str;
            };
            # TODO: assert at least one of these is true
            # NOTE: this currently needs to be done at the top level module
            ensureAccess = mkOption {
              type = types.attrsOf (types.submodule {
                options = {
                  read = mkOption {
                    type = types.bool;
                    default = false;
                  };
                  write = mkOption {
                    type = types.bool;
                    default = false;
                  };
                  owner = mkOption {
                    type = types.bool;
                    default = false;
                  };
                };
              });
              default = [];
            };
          };
        });
        default = {};
      };
    };
  };

  config = lib.mkIf config.fediversity.enable {
    environment.systemPackages = [ pkgs.minio-client pkgs.awscli ];

    networking.firewall.allowedTCPPorts = [
      fedicfg.rpc.port
    ];
    services.garage = {
      enable = true;
      package = pkgs.garage_0_9;
      settings = {
        replication_mode = "none";
        # TODO: use a secret file
        rpc_secret = "d576c4478cc7d0d94cfc127138cbb82018b0155c037d1c827dfb6c36be5f6625";
        # TODO: why does this have to be set? is there not a sensible default?
        rpc_bind_addr = "[::]:${toString fedicfg.rpc.port}";
        rpc_public_addr = "[::1]:${toString fedicfg.rpc.port}";
        s3_api.api_bind_addr = "[::]:${toString fedicfg.api.port}";
        s3_web.bind_addr = "[::]:${toString fedicfg.web.internalPort}";
        s3_web.root_domain = ".${fedicfg.web.rootDomain}";
        index = "index.html";

        s3_api.s3_region = "garage";
        s3_api.root_domain = ".${fedicfg.api.domain}";
      };
    };

    services.nginx.virtualHosts.${fedicfg.web.rootDomain} = {
      forceSSL = true;
      enableACME = true;
      serverAliases = lib.mapAttrsToList (bucket: _: fedicfg.web.domainForBucket bucket) cfg.ensureBuckets; ## TODO: use wildcard certificates?
      locations."/" = {
        proxyPass = "http://localhost:3902";
        extraConfig = ''
          proxy_set_header Host $host;
        '';
      };
    };

    systemd.services.ensure-garage = {
      after = [ "garage.service" ];
      wantedBy = [ "garage.service" ];
      serviceConfig = {
        Type = "oneshot";
      };
      path = [ cfg.package pkgs.perl pkgs.awscli ];
      script = ''
        set -xeuo pipefail

        # Give Garage time to start up by waiting until somethings speaks HTTP
        # behind Garage's API URL.
        until ${pkgs.curl}/bin/curl -sio /dev/null ${fedicfg.api.url}; do sleep 1; done

        # XXX: this is very sensitive to being a single instance
        # (doing the bare minimum to get garage up and running)
        # also, it's crazy that we have to parse command output like this
        # TODO: talk to garage maintainer about making this nicer to work with in Nix
        # before I do that though, I should figure out how setting it up across multiple machines will work
        GARAGE_ID=$(garage node id 2>/dev/null | perl -ne '/(.*)@.*/ && print $1')
        garage layout assign -z g1 -c 1G $GARAGE_ID
        LAYOUT_VER=$(garage layout show | perl -ne '/Current cluster layout version: (\d*)/ && print $1')
        garage layout apply --version $((LAYOUT_VER + 1))

        # XXX: this is a hack because we want to write to the buckets here but we're not guaranteed any access keys
        # TODO: generate this key here rather than using a well-known key
        # TODO: if the key already exists, we get an error; hacked with this `|| :` which needs to be removed
        garage key import --yes -n tmp ${snakeoil_key.id} ${snakeoil_key.secret} || :
        export AWS_ACCESS_KEY_ID=${snakeoil_key.id};
        export AWS_SECRET_ACCESS_KEY=${snakeoil_key.secret};

        ${ensureBucketsScript}
        ${ensureKeysScript}

        # garage doesn't like re-adding keys that once existed, so we can't delete / recreate it every time
        # garage key delete ${snakeoil_key.id} --yes
      '';
    };
  };
}