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

with lib;
let
  cfg = config.fudo.secrets;

  encrypt-on-disk = { secret-name, target-host, target-pubkey, source-file }:
    pkgs.stdenv.mkDerivation {
      name = "${target-host}-${secret-name}-secret";
      phases = "installPhase";
      buildInputs = [ pkgs.age ];
      installPhase = ''
        age -a -r "${target-pubkey}" -o $out ${source-file}
      '';
    };

  decrypt-script = { secret-name, source-file, target-host, target-file
    , host-master-key, user, group, permissions }:
    pkgs.writeShellScript
    "decrypt-fudo-secret-${target-host}-${secret-name}.sh" ''
      rm -rf ${target-file}
      age -d -i ${host-master-key.key-path} -o ${target-file} ${
        encrypt-on-disk {
          inherit secret-name source-file target-host;
          target-pubkey = host-master-key.public-key;
        }
      }
      chown ${user}:${group} ${target-file}
      chmod ${permissions} ${target-file}
    '';

  secret-service = target-host: secret-name:
    { source-file, target-file, user, group, permissions }: {
      description = "decrypt secret ${secret-name} for ${target-host}.";
      wantedBy = [ "multi-user.target" ];
      serviceConfig = {
        Type = "oneshot";
        ExecStartPre = pkgs.writeShellScript
          "prepare-${target-host}-${secret-name}-secret-dir.sh" ''
            TARGET_DIR=$(dirname ${target-file})
            if [[ ! -d "$TARGET_DIR" ]]; then
              mkdir -p "$TARGET_DIR"
            fi
          '';
        ExecStart = let
          host-master-key = config.fudo.hosts.${target-host}.master-key;
        in decrypt-script {
          inherit secret-name source-file target-host target-file host-master-key
            user group permissions;
        };
      };
      path = [ pkgs.age ];
    };

  secretOpts = { ... }: {
    options = with types; {
      source-file = mkOption {
        type = path; # CAREFUL: this will copy the file to nixstore...keep on deploy host
        description = "File from which to load the secret.";
      };

      target-file = mkOption {
        type = str;
        description =
          "Target file on the host; the secret will be decrypted to this file.";
      };

      user = mkOption {
        type = str;
        description = "User (on target host) to which the file will belong.";
      };

      group = mkOption {
        type = str;
        description = "Group (on target host) to which the file will belong.";
        default = "nogroup";
      };

      permissions = mkOption {
        type = str;
        description = "Permissions to set on the target file.";
        default = "0400";
      };
    };
  };

  nix-build-users = let usernames = attrNames config.users.users;
  in filter (user: (builtins.match "^nixbld[0-9]{1,2}$" user) != null)
  usernames;

in {
  options.fudo.secrets = with types; {
    enable = mkOption {
      type = bool;
      description = "Include secrets in the build (disable when secrets are unavailable)";
      default = true;
    };

    host-secrets = mkOption {
      type = attrsOf (attrsOf (submodule secretOpts));
      description = "Map of hosts to host secrets";
      default = { };
    };

    secret-users = mkOption {
      type = listOf str;
      description = "List of users with read-access to secrets.";
      default = [ ];
    };

    secret-group = mkOption {
      type = str;
      description = "Group to which secrets will belong.";
      default = "nixops-secrets";
    };

    secret-paths = mkOption {
      type = listOf str;
      description =
        "Paths which contain (only) secrets. The contents will be reabable by the secret-group.";
      default = [ ];
    };
  };

  config = mkIf cfg.enable {
    users.groups = {
      ${cfg.secret-group} = { members = cfg.secret-users ++ nix-build-users; };
    };

    systemd = let
      hostname = config.instance.hostname;
      host-secrets = if (hasAttr hostname cfg.host-secrets) then
        cfg.host-secrets.${hostname}
      else
        { };
      host-secret-services = mapAttrs' (secret: secretOpts:
        (nameValuePair "fudo-secret-${hostname}-${secret}"
          (secret-service hostname secret secretOpts))) host-secrets;

    in {
      services = host-secret-services // {
        fudo-secrets-watcher = {
          wantedBy = [ "multi-user.target" ];
          description =
            "Ensure access for group ${cfg.secret-group} to fudo secret paths.";
          serviceConfig = {
            ExecStart = pkgs.writeShellScript "fudo-secrets-watcher.sh"
              (concatStringsSep "\n" (map (path: ''
                chown -R root:${cfg.secret-group} ${path}
                chmod -R u=rwX,g=rX,o= ${path}
              '') cfg.secret-paths));
          };
        };
      };

      paths.fudo-secrets-watcher = mkIf ((length cfg.secret-paths) > 0) {
        wantedBy = [ "multi-user.target" ];
        description = "Watch fudo secret paths, and correct perms on changes.";
        pathConfig = {
          PathChanged = cfg.secret-paths;
          Unit = "fudo-secrets-watcher.service";
        };
      };

      tmpfiles.rules = map (path: "d '${path}' - root ${cfg.secret-group} - -")
        cfg.secret-paths;
    };
  };
}