{
  config,
  lib,
  pkgs,
  ...
}: let
  inherit (lib) mkIf mkOption types;
  inherit (lib.lists) all any filter findSingle length;
  inherit (lib.attrsets) attrValues hasAttr listToAttrs mapAttrs removeAttrs;

  inherit (config.sops) secrets;
  inherit (config.networking) hostName;

  cfg = config.services.borgbackup;
in {
  options.services.borgbackup = {
    configs = mkOption {
      # Taken from https://github.com/NixOS/nixpkgs/blob/master/nixos/modules/services/backup/borgbackup.nix
      type = types.attrsOf (types.submodule (
        let
          globalConfig = config;
        in
          {
            name,
            config,
            ...
          }: {
            options = {
              paths = mkOption {
                type = types.nullOr (types.coercedTo types.str lib.singleton (types.listOf types.str));
                default = null;
              };
              dumpCommand = mkOption {
                type = types.nullOr types.path;
                default = null;
              };
              repo = mkOption {
                type = types.str;
                default = name;
              };
              removableDevice = mkOption {
                type = types.bool;
                default = false;
              };
              archiveBaseName = mkOption {
                type = types.nullOr (types.strMatching "[^/{}]+");
                default = "${globalConfig.networking.hostName}-${name}";
              };
              dateFormat = mkOption {
                type = types.str;
                default = "+%Y-%m-%dT%H:%M:%S";
              };
              startAt = mkOption {
                type = with types; either str (listOf str);
                # Run every 3 hours
                default = "00/3:00";
              };
              persistentTimer = mkOption {
                default = false;
                type = types.bool;
              };
              inhibitsSleep = mkOption {
                default = false;
                type = types.bool;
              };
              user = mkOption {
                type = types.str;
                default = "root";
              };
              group = mkOption {
                type = types.str;
                default = "root";
              };
              encryption.mode = mkOption {
                type = types.enum [
                  "repokey"
                  "keyfile"
                  "repokey-blake2"
                  "keyfile-blake2"
                  "authenticated"
                  "authenticated-blake2"
                  "none"
                ];
                default = "none";
              };
              encryption.passCommand = mkOption {
                type = with types; nullOr str;
                default = null;
              };
              encryption.passphrase = mkOption {
                type = with types; nullOr str;
                default = null;
              };
              compression = mkOption {
                type = types.strMatching "none|(auto,)?(lz4|zstd|zlib|lzma)(,[[:digit:]]{1,2})?";
                default = "auto,lz4";
              };
              exclude = mkOption {
                type = with types; listOf str;
                default = [];
              };
              patterns = mkOption {
                type = with types; listOf str;
                default = [];
              };
              readWritePaths = mkOption {
                type = types.listOf types.path;
                default = [];
              };
              privateTmp = mkOption {
                type = types.bool;
                default = true;
              };
              doInit = mkOption {
                type = types.bool;
                default = true;
              };
              appendFailedSuffix = mkOption {
                type = types.bool;
                default = true;
              };
              prune.keep = mkOption {
                type = with types; attrsOf (either int (strMatching "[[:digit:]]+[Hdwmy]"));
                default = {};
              };
              prune.prefix = mkOption {
                type = types.nullOr (types.str);
                default = config.archiveBaseName;
              };
              environment = mkOption {
                type = with types; attrsOf str;
                default = {};
              };
              preHook = mkOption {
                type = types.lines;
                default = "";
              };
              postInit = mkOption {
                type = types.lines;
                default = "";
              };
              postCreate = mkOption {
                type = types.lines;
                default = "";
              };
              postPrune = mkOption {
                type = types.lines;
                default = "";
              };
              postHook = mkOption {
                type = types.lines;
                default = "";
              };
              extraArgs = mkOption {
                type = types.str;
                default = "";
              };
              extraInitArgs = mkOption {
                type = types.str;
                default = "";
              };
              extraCreateArgs = mkOption {
                type = types.str;
                default = "";
              };
              extraPruneArgs = mkOption {
                type = types.str;
                default = "";
              };
              extraCompactArgs = mkOption {
                type = types.str;
                default = "";
              };
            };
          }
      ));
      default = {};
    };

    existingRepos = mkOption {
      type = types.listOf (types.submodule {
        options = {
          name = mkOption {
            type = types.str;
          };
          host = mkOption {
            type = types.str;
          };
          authorizedKeys = mkOption {
            type = types.listOf types.str;
            default = [];
          };
        };
      });
      default = [];
    };
  };

  config = mkIf (cfg.configs != {}) {
    assertions = [
      {
        assertion = all (
          conf:
            any (repo: conf.repo == repo.name) cfg.existingRepos
        ) (attrValues cfg.configs);
        message = ''
          The  repo you want to backup to needs to exist.
        '';
      }
    ];

    services.borgbackup = let
      backupDir = {
        nos = "/data/borgbackups";
        servivi = "/home/backups";
      };
    in {
      repos =
        mkIf (length cfg.existingRepos > 0)
        (listToAttrs (map (r: {
            inherit (r) name;
            value = {
              authorizedKeysAppendOnly = r.authorizedKeys;
              path = "${backupDir.${hostName}}/${r.name}";
            };
          })
          (filter (x: x.host == hostName) cfg.existingRepos)));

      jobs = mapAttrs (n: v: let
        existingRepo = findSingle (x: x.name == v.repo) null null cfg.existingRepos;
        otherAttrs = removeAttrs v [
          "environment"
          "paths"
          "preHook"
          "postHook"
          "repo"
        ];
        pathPrefix = "/root/snaps";
        snapPath = "${pathPrefix}/${n}";
      in
        {
          environment =
            v.environment
            // (mkIf (hasAttr "borg-ssh" secrets) {
              BORG_RSH = "ssh -o 'StrictHostKeyChecking=no' -i ${secrets.borg-ssh.path}";
            });

          paths = map (x: snapPath + x) v.paths;

          preHook =
            v.preHook
            + ''
              if [[ ! -d ${pathPrefix} ]]; then
                  mkdir -p ${pathPrefix}
              fi

              ${pkgs.btrfs-progs}/bin/btrfs subvolume snapshot -r / ${snapPath}
            '';

          postHook =
            v.postHook
            + ''
              ${pkgs.btrfs-progs}/bin/btrfs subvolume delete ${snapPath}
            '';

          repo =
            if (hostName != existingRepo.host)
            then "ssh://borg@${existingRepo.host}${backupDir.${existingRepo.host}}/${v.repo}"
            else "${backupDir.${existingRepo.host}}/${v.repo}";
        }
        // otherAttrs)
      cfg.configs;
    };
  };

  # For accurate stack trace
  _file = ./module.nix;
}