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

with lib;

let

  cfg = config.services.rspamd;
  opt = options.services.rspamd;
  postfixCfg = config.services.postfix;

  bindSocketOpts =
    { options, config, ... }:
    {
      options = {
        socket = mkOption {
          type = types.str;
          example = "localhost:11333";
          description = ''
            Socket for this worker to listen on in a format acceptable by rspamd.
          '';
        };
        mode = mkOption {
          type = types.str;
          default = "0644";
          description = "Mode to set on unix socket";
        };
        owner = mkOption {
          type = types.str;
          default = "${cfg.user}";
          description = "Owner to set on unix socket";
        };
        group = mkOption {
          type = types.str;
          default = "${cfg.group}";
          description = "Group to set on unix socket";
        };
        rawEntry = mkOption {
          type = types.str;
          internal = true;
        };
      };
      config.rawEntry =
        let
          maybeOption = option: optionalString options.${option}.isDefined " ${option}=${config.${option}}";
        in
        if (!(hasPrefix "/" config.socket)) then
          "${config.socket}"
        else
          "${config.socket}${maybeOption "mode"}${maybeOption "owner"}${maybeOption "group"}";
    };

  traceWarning = w: x: builtins.trace "[1;31mwarning: ${w}[0m" x;

  workerOpts =
    { name, options, ... }:
    {
      options = {
        enable = mkOption {
          type = types.nullOr types.bool;
          default = null;
          description = "Whether to run the rspamd worker.";
        };
        name = mkOption {
          type = types.nullOr types.str;
          default = name;
          description = "Name of the worker";
        };
        type = mkOption {
          type = types.nullOr (
            types.enum [
              "normal"
              "controller"
              "fuzzy"
              "rspamd_proxy"
              "lua"
              "proxy"
            ]
          );
          description = ''
            The type of this worker. The type `proxy` is
            deprecated and only kept for backwards compatibility and should be
            replaced with `rspamd_proxy`.
          '';
          apply =
            let
              from = "services.rspamd.workers.\"${name}\".type";
              files = options.type.files;
              warning = "The option `${from}` defined in ${showFiles files} has enum value `proxy` which has been renamed to `rspamd_proxy`";
            in
            x: if x == "proxy" then traceWarning warning "rspamd_proxy" else x;
        };
        bindSockets = mkOption {
          type = types.listOf (types.either types.str (types.submodule bindSocketOpts));
          default = [ ];
          description = ''
            List of sockets to listen, in format acceptable by rspamd
          '';
          example = [
            {
              socket = "/run/rspamd.sock";
              mode = "0666";
              owner = "rspamd";
            }
            "*:11333"
          ];
          apply =
            value:
            map (
              each:
              if (isString each) then
                if (isUnixSocket each) then
                  {
                    socket = each;
                    owner = cfg.user;
                    group = cfg.group;
                    mode = "0644";
                    rawEntry = "${each}";
                  }
                else
                  {
                    socket = each;
                    rawEntry = "${each}";
                  }
              else
                each
            ) value;
        };
        count = mkOption {
          type = types.nullOr types.int;
          default = null;
          description = ''
            Number of worker instances to run
          '';
        };
        includes = mkOption {
          type = types.listOf types.str;
          default = [ ];
          description = ''
            List of files to include in configuration
          '';
        };
        extraConfig = mkOption {
          type = types.lines;
          default = "";
          description = "Additional entries to put verbatim into worker section of rspamd config file.";
        };
      };
      config =
        mkIf (name == "normal" || name == "controller" || name == "fuzzy" || name == "rspamd_proxy")
          {
            type = mkDefault name;
            includes = mkDefault [ "$CONFDIR/worker-${if name == "rspamd_proxy" then "proxy" else name}.inc" ];
            bindSockets =
              let
                unixSocket = name: {
                  mode = "0660";
                  socket = "/run/rspamd/${name}.sock";
                  owner = cfg.user;
                  group = cfg.group;
                };
              in
              mkDefault (
                if name == "normal" then
                  [ (unixSocket "rspamd") ]
                else if name == "controller" then
                  [ "localhost:11334" ]
                else if name == "rspamd_proxy" then
                  [ (unixSocket "proxy") ]
                else
                  [ ]
              );
          };
    };

  isUnixSocket = socket: hasPrefix "/" (if (isString socket) then socket else socket.socket);

  mkBindSockets =
    enabled: socks:
    concatStringsSep "\n  " (flatten (map (each: "bind_socket = \"${each.rawEntry}\";") socks));

  rspamdConfFile = pkgs.writeText "rspamd.conf" ''
    .include "$CONFDIR/common.conf"

    options {
      pidfile = "$RUNDIR/rspamd.pid";
      .include "$CONFDIR/options.inc"
      .include(try=true; priority=1,duplicate=merge) "$LOCAL_CONFDIR/local.d/options.inc"
      .include(try=true; priority=10) "$LOCAL_CONFDIR/override.d/options.inc"
    }

    logging {
      type = "syslog";
      .include "$CONFDIR/logging.inc"
      .include(try=true; priority=1,duplicate=merge) "$LOCAL_CONFDIR/local.d/logging.inc"
      .include(try=true; priority=10) "$LOCAL_CONFDIR/override.d/logging.inc"
    }

    ${concatStringsSep "\n" (
      mapAttrsToList (
        name: value:
        let
          includeName = if name == "rspamd_proxy" then "proxy" else name;
          tryOverride = boolToString (value.extraConfig == "");
        in
        ''
          worker "${value.type}" {
            type = "${value.type}";
            ${optionalString (value.enable != null) "enabled = ${lib.boolToYesNo (value.enable != false)};"}
            ${mkBindSockets value.enable value.bindSockets}
            ${optionalString (value.count != null) "count = ${toString value.count};"}
            ${concatStringsSep "\n  " (map (each: ".include \"${each}\"") value.includes)}
            .include(try=true; priority=1,duplicate=merge) "$LOCAL_CONFDIR/local.d/worker-${includeName}.inc"
            .include(try=${tryOverride}; priority=10) "$LOCAL_CONFDIR/override.d/worker-${includeName}.inc"
          }
        ''
      ) cfg.workers
    )}

    ${optionalString (cfg.extraConfig != "") ''
      .include(priority=10) "$LOCAL_CONFDIR/override.d/extra-config.inc"
    ''}
  '';

  filterFiles = files: filterAttrs (n: v: v.enable) files;
  rspamdDir = pkgs.linkFarm "etc-rspamd-dir" (
    (mapAttrsToList (name: file: {
      name = "local.d/${name}";
      path = file.source;
    }) (filterFiles cfg.locals))
    ++ (mapAttrsToList (name: file: {
      name = "override.d/${name}";
      path = file.source;
    }) (filterFiles cfg.overrides))
    ++ (optional (cfg.localLuaRules != null) {
      name = "rspamd.local.lua";
      path = cfg.localLuaRules;
    })
    ++ [
      {
        name = "rspamd.conf";
        path = rspamdConfFile;
      }
    ]
  );

  configFileModule =
    prefix:
    { name, config, ... }:
    {
      options = {
        enable = mkOption {
          type = types.bool;
          default = true;
          description = ''
            Whether this file ${prefix} should be generated.  This
            option allows specific ${prefix} files to be disabled.
          '';
        };

        text = mkOption {
          default = null;
          type = types.nullOr types.lines;
          description = "Text of the file.";
        };

        source = mkOption {
          type = types.path;
          description = "Path of the source file.";
        };
      };
      config = {
        source = mkIf (config.text != null) (
          let
            name' = "rspamd-${prefix}-" + baseNameOf name;
          in
          mkDefault (pkgs.writeText name' config.text)
        );
      };
    };

  configOverrides =
    (mapAttrs' (
      n: v:
      nameValuePair "worker-${if n == "rspamd_proxy" then "proxy" else n}.inc" {
        text = v.extraConfig;
      }
    ) (filterAttrs (n: v: v.extraConfig != "") cfg.workers))
    // (lib.optionalAttrs (cfg.extraConfig != "") {
      "extra-config.inc".text = cfg.extraConfig;
    });
in

{
  ###### interface

  options = {

    services.rspamd = {
      enable = mkEnableOption "rspamd, the Rapid spam filtering system";
      package = lib.mkPackageOption pkgs "rspamd" { };

      debug = mkOption {
        type = types.bool;
        default = false;
        description = "Whether to run the rspamd daemon in debug mode.";
      };

      locals = mkOption {
        type = with types; attrsOf (submodule (configFileModule "locals"));
        default = { };
        description = ''
          Local configuration files, written into {file}`/etc/rspamd/local.d/{name}`.
        '';
        example = literalExpression ''
          { "redis.conf".source = "/nix/store/.../etc/dir/redis.conf";
            "arc.conf".text = "allow_envfrom_empty = true;";
          }
        '';
      };

      overrides = mkOption {
        type = with types; attrsOf (submodule (configFileModule "overrides"));
        default = { };
        description = ''
          Overridden configuration files, written into {file}`/etc/rspamd/override.d/{name}`.
        '';
        example = literalExpression ''
          { "redis.conf".source = "/nix/store/.../etc/dir/redis.conf";
            "arc.conf".text = "allow_envfrom_empty = true;";
          }
        '';
      };

      localLuaRules = mkOption {
        default = null;
        type = types.nullOr types.path;
        description = ''
          Path of file to link to {file}`/etc/rspamd/rspamd.local.lua` for local
          rules written in Lua
        '';
      };

      workers = mkOption {
        type = with types; attrsOf (submodule workerOpts);
        description = ''
          Attribute set of workers to start.
        '';
        default = {
          normal = { };
          controller = { };
        };
        example = literalExpression ''
          {
            normal = {
              includes = [ "$CONFDIR/worker-normal.inc" ];
              bindSockets = [{
                socket = "/run/rspamd/rspamd.sock";
                mode = "0660";
                owner = "''${config.${opt.user}}";
                group = "''${config.${opt.group}}";
              }];
            };
            controller = {
              includes = [ "$CONFDIR/worker-controller.inc" ];
              bindSockets = [ "[::1]:11334" ];
            };
          }
        '';
      };

      extraConfig = mkOption {
        type = types.lines;
        default = "";
        description = ''
          Extra configuration to add at the end of the rspamd configuration
          file.
        '';
      };

      user = mkOption {
        type = types.str;
        default = "rspamd";
        description = ''
          User to use when no root privileges are required.
        '';
      };

      group = mkOption {
        type = types.str;
        default = "rspamd";
        description = ''
          Group to use when no root privileges are required.
        '';
      };

      postfix = {
        enable = mkOption {
          type = types.bool;
          default = false;
          description = "Add rspamd milter to postfix main.conf";
        };

        config = mkOption {
          type =
            with types;
            attrsOf (oneOf [
              bool
              str
              (listOf str)
            ]);
          description = ''
            Addon to postfix configuration
          '';
          default = {
            smtpd_milters = [ "unix:/run/rspamd/rspamd-milter.sock" ];
            non_smtpd_milters = [ "unix:/run/rspamd/rspamd-milter.sock" ];
          };
        };
      };
    };
  };

  ###### implementation

  config = mkIf cfg.enable {
    services.rspamd.overrides = configOverrides;
    services.rspamd.workers = mkIf cfg.postfix.enable {
      controller = { };
      rspamd_proxy = {
        bindSockets = [
          {
            mode = "0660";
            socket = "/run/rspamd/rspamd-milter.sock";
            owner = cfg.user;
            group = postfixCfg.group;
          }
        ];
        extraConfig = ''
          upstream "local" {
            default = yes; # Self-scan upstreams are always default
            self_scan = yes; # Enable self-scan
          }
        '';
      };
    };
    services.postfix.settings.main = mkIf cfg.postfix.enable cfg.postfix.config;

    systemd.services.postfix = mkIf cfg.postfix.enable {
      serviceConfig.SupplementaryGroups = [ postfixCfg.group ];
    };

    # Allow users to run 'rspamc' and 'rspamadm'.
    environment.systemPackages = [ cfg.package ];

    users.users.${cfg.user} = {
      description = "rspamd daemon";
      uid = config.ids.uids.rspamd;
      group = cfg.group;
    };

    users.groups.${cfg.group} = {
      gid = config.ids.gids.rspamd;
    };

    environment.etc.rspamd.source = rspamdDir;

    systemd.services.rspamd = {
      description = "Rspamd Service";

      wantedBy = [ "multi-user.target" ];
      after = [ "network.target" ];
      restartTriggers = [ rspamdDir ];

      serviceConfig = {
        ExecStart = "${cfg.package}/bin/rspamd ${optionalString cfg.debug "-d"} -c /etc/rspamd/rspamd.conf -f";
        Restart = "always";

        User = "${cfg.user}";
        Group = "${cfg.group}";
        SupplementaryGroups = mkIf cfg.postfix.enable [ postfixCfg.group ];

        RuntimeDirectory = "rspamd";
        RuntimeDirectoryMode = "0755";
        StateDirectory = "rspamd";
        StateDirectoryMode = "0700";

        AmbientCapabilities = [ ];
        CapabilityBoundingSet = "";
        DevicePolicy = "closed";
        LockPersonality = true;
        NoNewPrivileges = true;
        PrivateDevices = true;
        PrivateMounts = true;
        PrivateTmp = true;
        # we need to chown socket to rspamd-milter
        PrivateUsers = !cfg.postfix.enable;
        ProtectClock = true;
        ProtectControlGroups = true;
        ProtectHome = true;
        ProtectHostname = true;
        ProtectKernelLogs = true;
        ProtectKernelModules = true;
        ProtectKernelTunables = true;
        ProtectSystem = "strict";
        RemoveIPC = true;
        RestrictAddressFamilies = [
          "AF_INET"
          "AF_INET6"
          "AF_UNIX"
        ];
        RestrictNamespaces = true;
        RestrictRealtime = true;
        RestrictSUIDSGID = true;
        SystemCallArchitectures = "native";
        SystemCallFilter = "@system-service";
        UMask = "0077";
      };
    };
  };
  imports = [
    (mkRemovedOptionModule [
      "services"
      "rspamd"
      "socketActivation"
    ] "Socket activation never worked correctly and could at this time not be fixed and so was removed")
    (mkRenamedOptionModule
      [ "services" "rspamd" "bindSocket" ]
      [ "services" "rspamd" "workers" "normal" "bindSockets" ]
    )
    (mkRenamedOptionModule
      [ "services" "rspamd" "bindUISocket" ]
      [ "services" "rspamd" "workers" "controller" "bindSockets" ]
    )
    (mkRemovedOptionModule [
      "services"
      "rmilter"
    ] "Use services.rspamd.* instead to set up milter service")
  ];
}
