{ pkgs, lib, config, ... }:
with lib;
let cfg = config.security.systemd-creds; in
{
  options.security.systemd-creds = {
    target = mkOption {
      type = types.str;
      description = ''
        Destination address of the target host able to encrypt the credentials.
        Used by the default {option}`security.systemd-creds.shell`.
      '';
      default = "root@${config.networking.hostName}.${config.networking.domain}";
      defaultText = mdLiteral "root@\${config.networking.hostName}.\${config.networking.domain}";
      example = mdLiteral "''${config.networking.hostName}.wg";
    };
    decrypt = mkOption {
      type = with types; listOf str;
      description = mdDoc ''
        Command to get the cleartext of a credential.
        `credBase` is derived from the path of the credential in `LoadCredentialEncrypted=`,
        by removing the `builtins.storeDir` prefix and then one directory level.
      '';
      apply = concatStringsSep " ";
      default = [ "gpg" "--batch" "--decrypt" "\${credBase%.cred}.gpg" ];
      example = [ "pass" "\${credBase%.cred}" ];
    };
    shell = mkOption {
      type = with types; listOf str;
      description = mdDoc ''
        Command to get a shell on the target host.
      '';
      apply = concatStringsSep " ";
      default = [
        "ssh"
        "-o"
        "StrictHostKeyChecking=yes"
        "-o"
        "ControlMaster=auto"
        "-o"
        "ControlPersist=16s"
        "\"\${SYSTEMD_CREDS_TARGET:-${cfg.target}}\""
      ];
      defaultText = mdLiteral ''
        ssh -o StrictHostKeyChecking=yes \
            -o ControlMaster=auto \
            -o ControlPersist=16s \
            \"''${SYSTEMD_CREDS_TARGET:-root@''${config.security.systemd-creds.target}}\"
      '';
      example = [ "sudo" ];
    };
    encrypt = mkOption {
      type = with types; listOf str;
      description = mdDoc ''
        Command to run `systemd-creds encrypt` on the target host.

        ::: {.warning}
        Beware that the files `/etc/machine-id`
        and `/var/lib/systemd/credential.secret` on the target host,
        are both used to encrypt and decrypt when using the `host` key mechanism.
        Meaning that reinstalling the system on a new drive
        without restoring those two files
        will require to reencrypt the credentials.
        :::
      '';
      apply = concatStringsSep " ";
      default = [ "systemd-creds" "encrypt" "--name" "\"$credID\"" "--with-key=auto" ];
      example = [ "sudo" "systemd-creds" "encrypt" "--with-key=host" ];
    };
    install = mkOption {
      type = with types; listOf str;
      apply = concatStringsSep " ";
      description = mdDoc ''
        Command to install the encrypted credential on the orchestrating host.
      '';
      default = [ "install" "-D" "-m" "640" "/dev/stdin" "\"$credBase\"" ];
    };
    reencrypt = mkOption {
      type = types.lines;
      apply = pkgs.writeShellScriptBin "systemd-creds-encrypt-${replaceStrings ["@"] ["-"] cfg.target}";
      description = mdDoc ''
        Encrypt credentials referenced in the `LoadCredentialEncrypted=`
        of enabled systemd services, by running `systemd-creds` on the {option}`security.systemd-creds.target` host.
        Only *non-existing* credential files are considered,
        unless the `SYSTEMD_CREDS_FORCE_REENCRYPT` envvar is set to a non-empty value.
        Credential directories are not supported.

        Example of use:
        ```console
        $ sudo wg genkey | gpg --encrypt --sign --recipient @$USER --output wireguard/wg-intra/privateKey.gpg
        $ git add wireguard/wg-intra/privateKey.gpg
        ```

        ```nix
        { config, pkgs, lib, inputs, ... }:
        {
        systemd.services."wireguard-wg-intra".serviceConfig.LoadCredentialEncrypted =
          [ "privateKey:''${inputs.self}/wireguard/wg-intra/privateKey.cred" ];
        }
        ```

        ```console
        $ nix run .#nixosConfigurations.''${hostName}.config.security.systemd-creds.reencrypt
        $ git add wireguard/wg-intra/privateKey.cred
        ```

        ::: {.warning}
        To be able to access the relative path of the `.cred` file,
        `inputs.self` has to be used in `LoadCredentialEncrypted=`.
        Note that `inputs` is a `config._module.args` or `specialArgs`
        usually set in your `flake.nix`.
        In other words, using `''${wireguard/wg-intra/privatekey}` here,
        would not work, because it drops the `wireguard/wg-intra/` part.
        :::
      '';
    };
  };
  config = {
    security.systemd-creds.reencrypt = ''
      shopt -s extglob globstar nullglob
      set -o pipefail
      set -eux
    '' + concatMapStringsSep "\n"
      (service:
        concatMapStringsSep "\n"
          (credential:
            let
              cred = splitString ":" credential;
              credID = elemAt cred 0;
              credPath = elemAt cred 1;
            in
            ''
              credID=${escapeShellArg credID}
              credPath=${escapeShellArg credPath}
              credBase=''${credPath#${builtins.storeDir}/*/}
              if test "''${SYSTEMD_CREDS_FORCE_REENCRYPT:+set}" \
                      -o ! -s "$credBase" \
                      -o -e "''${credBase%.cred}.gpg" -a "$credBase" -ot "''${credBase%.cred}.gpg"; then
                { ${cfg.decrypt}; } |
                { ${cfg.shell} -- ${cfg.encrypt} - -; } |
                { ${cfg.install}; }
              fi
            ''
          )
          (toList service.serviceConfig.LoadCredentialEncrypted))
      (attrValues
        (filterAttrs
          (_serviceName: service:
            service.enable &&
              hasAttr "LoadCredentialEncrypted" service.serviceConfig
          )
          config.systemd.services
        )
      );
  };
}