{ pkgs, lib, config, ... }:
let inherit (builtins) baseNameOf readFile;
    inherit (lib) types;
    inherit (pkgs.lib) unlinesAttrs;
    inherit (config.services) openldap;
    inherit (config.users) ldap;
    # FIXME: readFIle ?
    copyFile = file: pkgs.writeText (baseNameOf file) (readFile file);
in
{
options = {
  services.openldap.domainSuffix = lib.mkOption {
    type        = types.str;
    default     = "dc=${lib.concatStringsSep ",dc=" (lib.splitString "." config.networking.domain)}";
    description = ''LDAP suffix for config.networking.domain.'';
  };
  services.openldap.initConfig = lib.mkOption {
    type = types.lines;
    description = "The databases' initial config in LDIF.";
    apply = lines: pkgs.writeText "cn=config.ldif.nix"
      (lines + "\n" + unlinesAttrs (olcSuffix: {conf, olcDbDirectory, ...}:
        "include: file://" + pkgs.writeText "config.ldif" (conf + ''
          olcSuffix: ${olcSuffix}
          olcDbDirectory: ${olcDbDirectory}
        '')
      ) openldap.databases);
    default = ''
      dn: cn=config
      objectClass: olcGlobal
      #olcPidFile: /run/slapd/slapd.pid
      # List of arguments that were passed to the server
      #olcArgsFile: /run/slapd/slapd.args
      # Read slapd-config(5) for possible values
      olcLogLevel: none
      # The tool-threads parameter sets the actual amount of CPU's
      # that is used for indexing.
      olcToolThreads: 1

      dn: olcDatabase={-1}frontend,cn=config
      objectClass: olcDatabaseConfig
      objectClass: olcFrontendConfig
      # The maximum number of entries that is returned for a search operation
      olcSizeLimit: 500
      # Allow unlimited access to local connection from the local root user
      olcAccess: to *
        by dn.exact=gidNumber=0+uidNumber=0,cn=peercred,cn=external,cn=auth manage
        by * break
      # Allow unauthenticated read access for schema and base DN autodiscovery
      olcAccess: to dn.exact=""
        by * read
      olcAccess: to dn.base="cn=Subschema"
        by * read

      dn: olcDatabase=config,cn=config
      objectClass: olcDatabaseConfig
      olcRootDN: cn=admin,cn=config
      # Access to cn=config, system root can be manager
      # with SASL mechanism (-Y EXTERNAL) over unix socket (-H ldapi://)
      olcAccess: to *
        by dn.exact="gidNumber=0+uidNumber=0,cn=peercred,cn=external,cn=auth" manage
        by * break

      dn: cn=schema,cn=config
      objectClass: olcSchemaConfig

      include: file://${pkgs.openldap}/etc/schema/core.ldif
      include: file://${pkgs.openldap}/etc/schema/cosine.ldif
      include: file://${pkgs.openldap}/etc/schema/nis.ldif
      include: file://${pkgs.openldap}/etc/schema/inetorgperson.ldif
      include: file://${copyFile openldap/schema/postfix-book.ldif}

      dn: cn=module{0},cn=config
      objectClass: olcModuleList
      # Where the dynamically loaded modules are stored
      #olcModulePath: /usr/lib/ldap
      olcModuleLoad: back_mdb
    '';
  };
  services.openldap.databases = lib.mkOption {
    default = {};
    type = types.attrsOf (types.submodule ({name, options, config, ...}: {
      options = {
        conf = lib.mkOption {
          type = types.lines;
          description = "The database's config in LDIF.";
        };
        data = lib.mkOption {
          type = types.lines;
          description = "The database's data in LDIF.";
        };
        olcDbDirectory = lib.mkOption {
          type = types.str;
          description = "The directory where the database is stored.";
          default = "${openldap.dataDir}/${name}";
        };
        resetData = lib.mkOption {
          type = types.bool;
          description = "Whether to reset the data at each start of the slapd service.";
          default = false;
        };
      };
    }));
  };
};
config = {
  systemd.services.openldap = {
    preStart = ''
        set -e
        # NOTE: slapd's config is always re-initialized.
        rm -rf "${openldap.configDir}"/cn=config \
               "${openldap.configDir}"/cn=config.ldif
        install -D -d -m 0700 -o "${openldap.user}" -g "${openldap.group}" "${openldap.configDir}"
        # NOTE: olcDbDirectory must be created before adding the config.
        '' +
        unlinesAttrs (olcSuffix: {data, olcDbDirectory, resetData, ...}:
          lib.optionalString resetData ''
            rm -rf "${olcDbDirectory}"
          '' + ''
          install -D -d -m 0700 -o "${openldap.user}" -g "${openldap.group}" "${olcDbDirectory}"
          '') openldap.databases
        + ''
        # NOTE: slapd is supposed to have been stopped by systemd
        # before entering this preStart,
        # hence slap* commands can safely be used.
        #
        # NOTE: slapadd(8):
        # To populate the config database slapd-config(5),
        # use -n 0 as it is always the first database.
        # It must physically exist on the filesystem prior to this, however.
        umask 0077
        ${pkgs.openldap}/bin/slapadd -n 0 \
         -F "${openldap.configDir}" \
         -l ${openldap.initConfig}
        chown -R "${openldap.user}:${openldap.group}" "${openldap.configDir}"
      '' +
      unlinesAttrs (olcSuffix: {data, olcDbDirectory, resetData, ...}:
        lib.optionalString resetData ''
          ${pkgs.openldap}/bin/slapadd \
           -F "${openldap.configDir}" \
           -l ${pkgs.writeText "data.ldif" data}
        '' + ''
        test ! -e "${olcDbDirectory}" ||
        chown -R "${openldap.user}:${openldap.group}" "${olcDbDirectory}"
      '') openldap.databases;
  };
};
}