{ 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}:
    nameValuePair "clerie-backup-${jobName}-${targetName}" {
      requires = [ "network.target" "local-fs.target" ];
      after = [ "network.target" "local-fs.target" ];
      path = [ pkgs.clerie-backup ];

      serviceConfig = {
        Type = "oneshot";
      };

      script = ''
        set -euo pipefail

        clerie-backup "${jobName}-${targetName}" backup

        ${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);

  backupConfigs = mergeAttrsList (map ({jobName, jobOptions, targetName, targetOptions}: let
      jobPasswordFile = if jobOptions.passwordFile != null then jobOptions.passwordFile else
        config.sops.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
        config.sops.secrets."clerie-backup-target-${targetName}".path;
      targetUsername = if targetOptions.username == null then config.networking.hostName else targetOptions.username;
    in {
      "clerie-backup/${jobName}-${targetName}/repo_password".source = jobPasswordFile;
      "clerie-backup/${jobName}-${targetName}/repo_url".text = "https://${targetOptions.serverName}${repoPath}";
      "clerie-backup/${jobName}-${targetName}/auth_username".text = targetUsername;
      "clerie-backup/${jobName}-${targetName}/auth_password".source = targetPasswordFile;
      "clerie-backup/${jobName}-${targetName}/files".text = concatStringsSep "\n" jobOptions.paths;
      "clerie-backup/${jobName}-${targetName}/excludes".text = concatStringsSep "\n" jobOptions.exclude;
    }
  ) 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 = [ pkgs.clerie-backup ];
    environment.etc = backupConfigs;
  };
}