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

with lib;

let
  forEachInstance = f: flip mapAttrs' config.services.fcgiwrap.instances (
    name: cfg: nameValuePair "fcgiwrap-${name}" (f cfg)
  );

in {
  imports = forEach [
    "enable"
    "user"
    "group"
    "socketType"
    "socketAddress"
    "preforkProcesses"
  ] (attr: mkRemovedOptionModule [ "services" "fcgiwrap" attr ] ''
      The global shared fcgiwrap instance is no longer supported due to
      security issues.
      Isolated instances should instead be configured through
      `services.fcgiwrap.instances.*'.
  '');

  options.services.fcgiwrap.instances = mkOption {
    description = "Configuration for fcgiwrap instances.";
    default = { };
    type = types.attrsOf (types.submodule ({ config, ... }: { options = {
      process.prefork = mkOption {
        type = types.ints.positive;
        default = 1;
        description = "Number of processes to prefork.";
      };

      process.user = mkOption {
        type = types.nullOr types.str;
        default = null;
        description = ''
          User as which this instance of fcgiwrap will be run.
          Set to `null` (the default) to use a dynamically allocated user.
        '';
      };

      process.group = mkOption {
        type = types.nullOr types.str;
        default = null;
        description = "Group as which this instance of fcgiwrap will be run.";
      };

      socket.type = mkOption {
        type = types.enum [ "unix" "tcp" "tcp6" ];
        default = "unix";
        description = "Socket type: 'unix', 'tcp' or 'tcp6'.";
      };

      socket.address = mkOption {
        type = types.str;
        default = "/run/fcgiwrap-${config._module.args.name}.sock";
        example = "1.2.3.4:5678";
        description = ''
          Socket address.
          In case of a UNIX socket, this should be its filesystem path.
        '';
      };

      socket.user = mkOption {
        type = types.nullOr types.str;
        default = null;
        description = ''
          User to be set as owner of the UNIX socket.
        '';
      };

      socket.group = mkOption {
        type = types.nullOr types.str;
        default = null;
        description = ''
          Group to be set as owner of the UNIX socket.
        '';
      };

      socket.mode = mkOption {
        type = types.nullOr types.str;
        default = if config.socket.type == "unix" then "0600" else null;
        defaultText = literalExpression ''
          if config.socket.type == "unix" then "0600" else null
        '';
        description = ''
          Mode to be set on the UNIX socket.
          Defaults to private to the socket's owner.
        '';
      };
    }; }));
  };

  config = {
    assertions = concatLists (mapAttrsToList (name: cfg: [
      {
        assertion = cfg.socket.type == "unix" -> cfg.socket.user != null;
        message = "Socket owner is required for the UNIX socket type.";
      }
      {
        assertion = cfg.socket.type == "unix" -> cfg.socket.group != null;
        message = "Socket owner is required for the UNIX socket type.";
      }
      {
        assertion = cfg.socket.user != null -> cfg.socket.type == "unix";
        message = "Socket owner can only be set for the UNIX socket type.";
      }
      {
        assertion = cfg.socket.group != null -> cfg.socket.type == "unix";
        message = "Socket owner can only be set for the UNIX socket type.";
      }
      {
        assertion = cfg.socket.mode != null -> cfg.socket.type == "unix";
        message = "Socket mode can only be set for the UNIX socket type.";
      }
    ]) config.services.fcgiwrap.instances);

    systemd.services = forEachInstance (cfg: {
      after = [ "nss-user-lookup.target" ];
      wantedBy = optional (cfg.socket.type != "unix") "multi-user.target";

      serviceConfig = {
        ExecStart = ''
          ${pkgs.fcgiwrap}/sbin/fcgiwrap ${cli.toGNUCommandLineShell {} ({
            c = cfg.process.prefork;
          } // (optionalAttrs (cfg.socket.type != "unix") {
            s = "${cfg.socket.type}:${cfg.socket.address}";
          }))}
        '';
      } // (if cfg.process.user != null then {
        User = cfg.process.user;
        Group = cfg.process.group;
      } else {
        DynamicUser = true;
      });
    });

    systemd.sockets = forEachInstance (cfg: mkIf (cfg.socket.type == "unix") {
      wantedBy = [ "sockets.target" ];
      socketConfig = {
        ListenStream = cfg.socket.address;
        SocketUser = cfg.socket.user;
        SocketGroup = cfg.socket.group;
        SocketMode = cfg.socket.mode;
      };
    });
  };
}
