# 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;
  };
}