# A re-implementation of the Borgmatic service home-manager module. The
# reimplementation basically separates all of the configurations instead of a
# oneshot where it will execute Borgmatic with all present configurations
# (which is fine but too overwhelming for my taste).
#
# It has an added integration for individual Borgmatic configurations from
# `programs.borgmatic.backups` (also a reimplemented version from the upstream)
# to be added to the jobset and has more control over each service unit.
#
# As a design constraint, you should still be able to do what the upstream
# service module with a little bit of elbow grease.
{ config, lib, pkgs, ... }:

let
  cfg = config.services.borgmatic;
  programCfg = config.programs.borgmatic;
  settingsFormat = pkgs.formats.yaml { };

  borgmaticProgramModule = { name, lib, ... }: {
    options = {
      initService = {
        enable = lib.mkEnableOption
          "include this particular backup as part of Borgmatic jobset at {option}`services.borgmatic.jobs`";

        startAt = lib.mkOption {
          type = lib.types.nonEmptyStr;
          description = ''
            Indicates how often the associated service occurs. Accepts value as
            found from {manpage}`systemd.time(7)`.
          '';
          default = "daily";
          example = "04:30";
        };
      };
    };
  };

  borgmaticJobModule = { config, lib, name, ... }:
    let
      settingsFile =
        settingsFormat.generate "borgmatic-job-config-${name}" config.settings;
    in {
      options = {
        settings = lib.mkOption {
          type = settingsFormat.type;
          description = ''
            Configuration settings associated with the job. If this is set, the
            generated output is added as an additional argument (i.e., `--config
            SETTINGSFILE`) in the service script.
          '';
          default = { };
          example = lib.literalExpression ''
            {
              source_directories = [
                config.xdg.userDirs.document
                config.xdg.userDirs.download
                config.xdg.userDirs.music
                config.xdg.userDirs.video
              ];

              keep_daily = 5;
              keep_weekly = 10;
              keep_monthly = 20;

              repositories = lib.singleton {
                path = "ssh://asodajdoiasjdoij";
                label = "remote";
              };
            }
          '';
        };

        startAt = lib.mkOption {
          type = lib.types.nonEmptyStr;
          description = ''
            Indicates how often backup will occur. This is to be used as value
            for `Timer.OnCalendar=` in the systemd unit. See
            {manpage}`systemd.time(7)` for more details.
          '';
          default = "daily";
          example = "04:30";
        };

        extraArgs = lib.mkOption {
          type = with lib.types; listOf str;
          description = ''
            List of arguments to be passed to the Borgmatic backup service.
          '';
          default = [ ];
          example = lib.literalExpression ''
            [
              "--stats"
              "--verbosity" "1"
              "--syslog-verbosity" "1"
              "--list"
            ]
          '';
        };
      };

      config = {
        extraArgs = lib.mkMerge [
          cfg.extraArgs

          (lib.optionals (config.settings != { })
            (lib.mkBefore [ "--config" settingsFile ]))
        ];
      };
    };

  formatUnitName = name: "borgmatic-job-${name}";
  mkBorgmaticServiceUnit = n: v:
    lib.nameValuePair (formatUnitName n) {
      Unit = {
        Description = "Borgmatic backup job '${n}'";
        Documentation =
          [ "https://torsion.org/borgmatic/docs/reference/configuration" ];
        ConditionACPower = true;
        StartLimitBurst = 5;
      };

      Service = {
        # TODO: Just cargo-culted from the upstream home-manager module. Will
        # need more info on this.
        Nice = 19;
        IOSchedulingClass = "best-effort";
        IOSchedulingPriority = 7;
        IOWeight = 100;

        Restart = "on-failure";
        LogRateLimitIntervalSec = 0;

        ExecStart = ''
          ${lib.getExe' cfg.package "borgmatic"} ${
            lib.concatStringsSep " " v.extraArgs
          }
        '';

        PrivateTmp = true;
      };
    };

  mkBorgmaticTimerUnit = n: v:
    lib.nameValuePair (formatUnitName n) {
      Unit.Description = "Borgmatic backup job '${n}'";

      Timer = {
        OnCalendar = v.startAt;
        Persistent = lib.mkDefault true;
        RandomizedDelaySec = lib.mkDefault "10m";
      };

      Install.WantedBy = [ "timers.target" ];
    };

  mkBorgmaticServiceFromConfig = n: v:
    lib.nameValuePair "borgmatic-config-${n}" {
      inherit (v.initService) startAt;
      extraArgs =
        [ "--config" "${config.xdg.configHome}/borgmatic.d/${n}.yaml" ];
    };
in {
  disabledModules = [ "services/borgmatic.nix" ];
  options.programs.borgmatic.backups = lib.mkOption {
    type = with lib.types; attrsOf (submodule borgmaticProgramModule);
  };

  options.services.borgmatic = {
    package = lib.mkPackageOption pkgs "borgmatic" { };

    extraArgs = lib.mkOption {
      type = with lib.types; listOf str;
      description = ''
        Global list of additional arguments for all of the jobs.
      '';
      default = [ ];
      example = [ "--stats" "--verbosity" "1" ];
    };

    jobs = lib.mkOption {
      type = with lib.types; attrsOf (submodule borgmaticJobModule);
      default = { };
      example = lib.literalExpression ''
        {
          personal = {
            startAt = "05:30";
            settings = {
              source_directories = [
                "''${config.xdg.configHome}"
                "''${config.xdg.userDirs.extraConfig.XDG_PROJECTS_DIR}"
                "''${config.home.homeDirectory}/.thunderbird"
                "''${config.home.homeDirectory}/Zotero"
              ];

              repositories = [
                {
                  path = "ssh://k8pDxu32@k8pDxu32.repo.borgbase.com/./repo";
                  label = "borgbase";
                }

                {
                  path = "/var/lib/backups/local.borg";
                  label = "local";
                }
              ];

              keep_daily = 7;
              keep_weekly = 4;
              keep_monthly = 6;
            };
          };
        }
      '';
    };
  };

  config = {
    systemd.user.services = lib.mapAttrs' mkBorgmaticServiceUnit cfg.jobs;

    systemd.user.timers = lib.mapAttrs' mkBorgmaticTimerUnit cfg.jobs;

    services.borgmatic.jobs = let
      validService =
        lib.filterAttrs (n: v: v.initService.enable) programCfg.backups;
    in lib.mapAttrs' mkBorgmaticServiceFromConfig validService;
  };
}
