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

let
  inherit (lib) mapAttrs' nameValuePair mkMerge mkOption mkIf any types;

  hackterpolate = s: let
    parts = builtins.split "(\\\$\\{[^}]+})" s;
    unescape = s: builtins.fromJSON ''"${s}"'';
    strParts = lib.imap1 (i: v: if lib.mod i 2 == 0 then unescape (builtins.elemAt v 0) else v) parts;
  in lib.concatStrings strParts;

  cfg = config.my.acme;
  accountsEndpoints = mapAttrs' (name: value: nameValuePair "acme_account_${name}" {
    depends_on = [ "vault_mount.acme" ];
    path = "acme/accounts/${name}";
    disable_read = true;

    data_json = hackterpolate (builtins.toJSON value);
  }) cfg.accounts;
  rolesEndpoints = mapAttrs' (name: value: nameValuePair "acme_role_${name}" {
    depends_on = [ "vault_mount.acme" ];
    path = "acme/roles/${name}";
    disable_read = true;

    data_json = hackterpolate (builtins.toJSON value);
  }) cfg.roles;

  mkMergeIf = things: mkIf (any (t: t != { }) things) (mkMerge things);
in {
  options = {
    my.acme.mountPoint = mkOption {
      default = "acme";
      type = types.str;
    };

    my.acme.accounts = mkOption {
      default = {};
      type = (pkgs.formats.json { }).type;
    };

    my.acme.roles = mkOption {
      default = {};
      type = types.attrsOf (types.submodule ({ name, ... }: {
        options = {
          account = mkOption {
            type = types.str;
            default = name;
          };

          allow_bare_domains = mkOption {
            type = types.bool;
            default = true;
          };

          allow_subdomains = mkOption {
            type = types.bool;
            default = true;
          };

          allowed_domains = mkOption {
            type = with types; listOf str;
          };

          cache_for_ratio = mkOption {
            type = types.int;
            default = 20;
          };

          disable_cache = mkOption {
            type = types.bool;
            default = false;
          };
        };
      }));
    };
  };

  config = {
    resource.vault_mount.acme = {
      path = config.my.acme.mountPoint;
      type = "acme";

      max_lease_ttl_seconds = 90 * 86400;
      default_lease_ttl_seconds = 90 * 86400;
    };

    resource.vault_generic_endpoint = mkMergeIf [
      accountsEndpoints
      rolesEndpoints
    ];
  };
}