{ config, lib, pkgs, ... }:

with lib;

let
  cfg = config.clerie.backup;

  jobTargetPairs = flatten (
    mapAttrsToList (jobName: jobOptions:
      let
        jobTargets = if jobOptions.targets == null then cfg.targets
        else
          filterAttrs (targetName: targetOptions:
            any (map (jobOptionTarget: jobOptionTarget == targetName) jobOptions.targets)
          ) cfg.targets;
      in mapAttrsToList (targetName: targetOptions:
        {
          inherit jobName jobOptions targetName targetOptions;
        }
      ) jobTargets
    ) cfg.jobs
  );

  backupServiceUnits = listToAttrs (map ({jobName, jobOptions, targetName, targetOptions}: let
    jobPasswordFile = if jobOptions.passwordFile != null then jobOptions.passwordFile else
      if builtins.elem "clerie-backup-job-${jobName}" (attrNames config.sops.secrets) then config.sops.secrets."clerie-backup-job-${jobName}".path else
        config.age.secrets."clerie-backup-job-${jobName}".path;
    repoPath = if jobOptions.repoPath == null then "/${config.networking.hostName}/${jobName}" else jobOptions.repoPath;
    targetPasswordFile = if targetOptions.passwordFile != null then targetOptions.passwordFile else
      if builtins.elem "clerie-backup-target-${targetName}" (attrNames config.sops.secrets) then config.sops.secrets."clerie-backup-target-${targetName}".path else
        config.age.secrets."clerie-backup-target-${targetName}".path;
    targetUsername = if targetOptions.username == null then config.networking.hostName else targetOptions.username;
  in
    nameValuePair "clerie-backup-${jobName}-${targetName}" {
      requires = [ "network.target" "local-fs.target" ];
      after = [ "network.target" "local-fs.target" ];
      path = [ pkgs.restic ];

      serviceConfig = {
        Type = "oneshot";
      };

      script = ''
        set -euo pipefail

        export RESTIC_PASSWORD_FILE=${jobPasswordFile}
        export RESTIC_REPOSITORY="rest:https://${targetUsername}:$(cat ${targetPasswordFile})@${targetOptions.serverName}${repoPath}"
        export RESTIC_PROGRESS_FPS=0.1
        export RESTIC_CACHE_DIR=/var/cache/restic

        restic snapshots --latest 1 || restic init

        restic backup ${optionalString (jobOptions.exclude != []) "--exclude-file ${pkgs.writeText "clerie-backup-${jobName}-${targetName}-excludes" (concatStringsSep "\n" jobOptions.exclude)}"} ${escapeShellArgs jobOptions.paths}

        ${optionalString (config.clerie.monitoring.enable) ''
          echo "clerie_backup_last_successful_run_time{backup_job=\"${jobName}\", backup_target=\"${targetName}\"} $(date +%s)" > /var/lib/prometheus-node-exporter/textfiles/clerie-backup-${jobName}-${targetName}.prom
        ''}
      '';
    }
  ) jobTargetPairs);

  backupServiceTimers = listToAttrs (map ({jobName, jobOptions, targetName, targetOptions}:
    nameValuePair "clerie-backup-${jobName}-${targetName}" {
      wantedBy = [ "timers.target" ];
      timerConfig = {
        OnCalendar = "hourly";
        RandomizedDelaySec = "1h";
      };
      requires = [ "network-online.target" ];
      after = [ "network-online.target" ];
    }
  ) jobTargetPairs);

  backupCommands = map ({jobName, jobOptions, targetName, targetOptions}: let
      jobPasswordFile = if jobOptions.passwordFile != null then jobOptions.passwordFile else
        if builtins.elem "clerie-backup-job-${jobName}" (attrNames config.sops.secrets) then config.sops.secrets."clerie-backup-job-${jobName}".path else
          config.age.secrets."clerie-backup-job-${jobName}".path;
      repoPath = if jobOptions.repoPath == null then "/${config.networking.hostName}/${jobName}" else jobOptions.repoPath;
      targetPasswordFile = if targetOptions.passwordFile != null then targetOptions.passwordFile else
        if builtins.elem "clerie-backup-target-${targetName}" (attrNames config.sops.secrets) then config.sops.secrets."clerie-backup-target-${targetName}".path else
          config.age.secrets."clerie-backup-target-${targetName}".path;
      targetUsername = if targetOptions.username == null then config.networking.hostName else targetOptions.username;
    in pkgs.writeShellApplication {
      name = "clerie-backup-${jobName}-${targetName}";

      runtimeInputs = [ pkgs.restic ];

      text = ''
        set -euo pipefail

        export RESTIC_PASSWORD_FILE=${jobPasswordFile}
        export RESTIC_REPOSITORY="rest:https://${targetUsername}:$(cat ${targetPasswordFile})@${targetOptions.serverName}${repoPath}"
        export RESTIC_PROGRESS_FPS=0.1
        export RESTIC_CACHE_DIR=/var/cache/restic

        restic "$@"
      '';

      checkPhase = "";
    }
  ) jobTargetPairs;

  targetOptions = { ... }: {
    options = {
      passwordFile = mkOption {
        type = with types; nullOr str;
        default = null;
      };
      username = mkOption {
        type = with types; nullOr str;
        default = null;
      };
      serverName = mkOption {
        type = types.str;
      };
    };
  };

  jobOptions = { ... }: {
    options = {
      passwordFile = mkOption {
        type = with types; nullOr str;
        default = null;
      };
      repoPath = mkOption {
        type = with types; nullOr str;
        default = null;
      };
      targets = mkOption {
        type = with types; nullOr (listOf str);
        default = null;
      };
      paths = mkOption {
        type = with types; listOf str;
      };
      exclude = mkOption {
        type = with types; listOf str;
        default = [];
      };
    };
  };
in

{
  options = {
    clerie.backup = {
      enable = mkEnableOption "clerie's Backup";
      targets = mkOption {
        type = with types; attrsOf (submodule targetOptions);
        default = {};
      };
      jobs = mkOption {
        type = with types; attrsOf (submodule jobOptions);
        default = {};
      };
    };
  };

  config = mkIf cfg.enable {
    systemd.services = backupServiceUnits;
    systemd.timers = backupServiceTimers;
    systemd.tmpfiles.rules = [
      "d /var/cache/restic - - - - -"
    ];
    environment.systemPackages = backupCommands;
  };
}