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

with lib;
with lib.plusultra;
let
  cfg = config.plusultra.services.vault;

  package = if cfg.ui then pkgs.vault-bin else pkgs.vault;

  has-policies = (builtins.length (builtins.attrNames cfg.policies)) != 0;

  format-policy = name: file: pkgs.runCommandNoCC
    "formatted-vault-policy"
    {
      inherit file;
      buildInputs = [ package ];
    }
    ''
      name="$(basename "$file")"

      cp "$file" "./$name"

      # Ensure that vault can overwrite the file.
      chmod +w "./$name"

      # Create this variable here to avoid swallowing vault's exit code.
      vault_output=

      set +e
      vault_output=$(vault policy fmt "./$name" 2>&1)
      vault_status=$?
      set -e

      if [ "$vault_status" != 0 ]; then
        echo 'Error formatting policy "${name}"'
        echo "This is normally caused by a syntax error in the policy file."
        echo "$file"
        echo ""
        echo "Vault Output:"
        echo "$vault_output"
        exit 1
      fi

      mv "./$name" $out
    '';

  policies = mapAttrs
    (name: value:
      if builtins.isPath value then
        format-policy name value
      else
        format-policy name (pkgs.writeText "${name}.hcl" value)
    )
    cfg.policies;
in
{
  options.plusultra.services.vault = {
    enable = mkEnableOption "Vault";

    ui = mkBoolOpt true "Whether the UI should be enabled.";

    storage = {
      backend = mkOpt types.str "file" "The storage backend for Vault.";
    };

    settings = mkOpt types.str "" "Configuration for Vault's config file.";

    mutable-policies = mkBoolOpt false "Whether policies not specified in Nix should be removed.";

    policies = mkOpt (types.attrsOf (types.either types.str types.path)) { } "Policies to install when Vault runs.";

    policy-agent = {
      user = mkOpt types.str "vault" "The user to run the Vault Agent as.";
      group = mkOpt types.str "vault" "The group to run the Vault Agent as.";

      auth = {
        roleIdFilePath = mkOpt types.str "/var/lib/vault/role-id" "The file to read the role-id from.";
        secretIdFilePath = mkOpt types.str "/var/lib/vault/secret-id" "The file to read the secret-id from.";
      };
    };
  };

  config = mkIf cfg.enable {
    services.vault = {
      enable = true;
      inherit package;

      extraConfig = ''
        ui = ${if cfg.ui then "true" else "false"}

        ${cfg.settings}
      '';

    };

    systemd.services.vault = { };

    systemd.services.vault-policies = mkIf (has-policies || !cfg.mutable-policies) {
      wantedBy = [ "vault.service" ];
      after = [ "vault.service" ];

      serviceConfig = {
        Type = "oneshot";
        User = cfg.policy-agent.user;
        Group = cfg.policy-agent.group;
        Restart = "on-failure";
        RestartSec = 30;
        RemainAfterExit = "yes";
      };

      restartTriggers = (mapAttrsToList (name: value: "${name}=${value}") policies);

      path = [
        package
        pkgs.curl
        pkgs.jq
      ];

      environment = {
        VAULT_ADDR = "http://${config.services.vault.address}";
      };

      script =
        let
          write-policies-commands = mapAttrsToList
            (name: policy:
              ''
                echo Writing policy '${name}': '${policy}'
                vault policy write '${name}' '${policy}'
              ''
            )
            policies;
          write-policies = concatStringsSep "\n" write-policies-commands;

          known-policies = mapAttrsToList (name: value: name) policies;

          remove-unknown-policies = ''
            current_policies=$(vault policy list -format=json | jq -r '.[]')
            known_policies=(${concatStringsSep " " (builtins.map (policy: "\"${policy}\"") known-policies)})

            while read current_policy; do
              is_known=false

              for known_policy in "''${known_policies[@]}"; do
                if [ "$known_policy" = "$current_policy" ]; then
                  is_known=true
                  break
                fi
              done

              if [ "$is_known" = "false" ] && [ "$current_policy" != "default" ] && [ "$current_policy" != "root" ]; then
                echo "Removing policy: $current_policy"
                vault policy delete "$current_policy"
              else
                echo "Keeping policy: $current_policy"
              fi
            done <<< "$current_policies"
          '';
        in
        ''
          if ! [ -f '${cfg.policy-agent.auth.roleIdFilePath}' ]; then
            echo 'role-id file not found: ${cfg.policy-agent.auth.roleIdFilePath}'
            exit 1
          fi

          if ! [ -f '${cfg.policy-agent.auth.secretIdFilePath}' ]; then
            echo 'secret-id file not found: ${cfg.policy-agent.auth.secretIdFilePath}'
            exit 1
          fi

          role_id="$(cat '${cfg.policy-agent.auth.roleIdFilePath}')"
          secret_id="$(cat '${cfg.policy-agent.auth.secretIdFilePath}')"

          seal_status=$(curl -s "$VAULT_ADDR/v1/sys/seal-status" | jq ".sealed")

          echo "Seal Status: $seal_status"

          if [ seal_status = "true" ]; then
            echo "Vault is currently sealed, cannot install policies."
            exit 1
          fi

          echo "Getting token..."

          token=$(vault write -field=token auth/approle/login \
            role_id="$role_id" \
            secret_id="$secret_id" \
          )

          echo "Logging in..."

          export VAULT_TOKEN="$(vault login -method=token -token-only token="$token")"

          echo "Writing policies..."

          ${write-policies}

          ${optionalString (!cfg.mutable-policies) remove-unknown-policies}
        '';
    };
  };
}
