{
  config,
  lib,
  pkgs,
  ...
}:
let
  cfg = config.services.deluge;
  cfg_web = config.services.deluge.web;
  isDeluge1 = lib.versionOlder cfg.package.version "2.0.0";

  openFilesLimit = 4096;
  listenPortsDefault = [
    6881
    6889
  ];

  listToRange = x: {
    from = lib.elemAt x 0;
    to = lib.elemAt x 1;
  };

  configDir = "${cfg.dataDir}/.config/deluge";
  configFile = pkgs.writeText "core.conf" (builtins.toJSON cfg.config);
  declarativeLockFile = "${configDir}/.declarative";

  preStart =
    if cfg.declarative then
      ''
        if [ -e ${declarativeLockFile} ]; then
          # Was declarative before, no need to back up anything
          ${if isDeluge1 then "ln -sf" else "cp"} ${configFile} ${configDir}/core.conf
          ln -sf ${cfg.authFile} ${configDir}/auth
        else
          # Declarative for the first time, backup stateful files
          ${if isDeluge1 then "ln -s" else "cp"} -b --suffix=.stateful ${configFile} ${configDir}/core.conf
          ln -sb --suffix=.stateful ${cfg.authFile} ${configDir}/auth
          echo "Autogenerated file that signifies that this server configuration is managed declaratively by NixOS" \
            > ${declarativeLockFile}
        fi
      ''
    else
      ''
        if [ -e ${declarativeLockFile} ]; then
          rm ${declarativeLockFile}
        fi
      '';
in
{
  options = {
    services = {
      deluge = {
        enable = lib.mkEnableOption "Deluge daemon";

        openFilesLimit = lib.mkOption {
          default = openFilesLimit;
          type = lib.types.either lib.types.int lib.types.str;
          description = ''
            Number of files to allow deluged to open.
          '';
        };

        config = lib.mkOption {
          type = lib.types.attrs;
          default = { };
          example = lib.literalExpression ''
            {
              download_location = "/srv/torrents/";
              max_upload_speed = "1000.0";
              share_ratio_limit = "2.0";
              allow_remote = true;
              daemon_port = 58846;
              listen_ports = [ ${toString listenPortsDefault} ];
            }
          '';
          description = ''
            Deluge core configuration for the core.conf file. Only has an effect
            when {option}`services.deluge.declarative` is set to
            `true`. String values must be quoted, integer and
            boolean values must not. See
            <https://git.deluge-torrent.org/deluge/tree/deluge/core/preferencesmanager.py#n41>
            for the available options.
          '';
        };

        declarative = lib.mkOption {
          type = lib.types.bool;
          default = false;
          description = ''
            Whether to use a declarative deluge configuration.
            Only if set to `true`, the options
            {option}`services.deluge.config`,
            {option}`services.deluge.openFirewall` and
            {option}`services.deluge.authFile` will be
            applied.
          '';
        };

        openFirewall = lib.mkOption {
          default = false;
          type = lib.types.bool;
          description = ''
            Whether to open the firewall for the ports in
            {option}`services.deluge.config.listen_ports`. It only takes effet if
            {option}`services.deluge.declarative` is set to
            `true`.

            It does NOT apply to the daemon port nor the web UI port. To access those
            ports securely check the documentation
            <https://dev.deluge-torrent.org/wiki/UserGuide/ThinClient#CreateSSHTunnel>
            or use a VPN or configure certificates for deluge.
          '';
        };

        dataDir = lib.mkOption {
          type = lib.types.path;
          default = "/var/lib/deluge";
          description = ''
            The directory where deluge will create files.
          '';
        };

        authFile = lib.mkOption {
          type = lib.types.path;
          example = "/run/keys/deluge-auth";
          description = ''
            The file managing the authentication for deluge, the format of this
            file is straightforward, each line contains a
            username:password:level tuple in plaintext. It only has an effect
            when {option}`services.deluge.declarative` is set to
            `true`.
            See <https://dev.deluge-torrent.org/wiki/UserGuide/Authentication> for
            more information.
          '';
        };

        user = lib.mkOption {
          type = lib.types.str;
          default = "deluge";
          description = ''
            User account under which deluge runs.
          '';
        };

        group = lib.mkOption {
          type = lib.types.str;
          default = "deluge";
          description = ''
            Group under which deluge runs.
          '';
        };

        extraPackages = lib.mkOption {
          type = lib.types.listOf lib.types.package;
          default = [ ];
          description = ''
            Extra packages available at runtime to enable Deluge's plugins. For example,
            extraction utilities are required for the built-in "Extractor" plugin.
            This always contains unzip, gnutar, xz and bzip2.
          '';
        };

        package = lib.mkPackageOption pkgs "deluge-2_x" { };
      };

      deluge.web = {
        enable = lib.mkEnableOption "Deluge Web daemon";

        port = lib.mkOption {
          type = lib.types.port;
          default = 8112;
          description = ''
            Deluge web UI port.
          '';
        };

        openFirewall = lib.mkOption {
          type = lib.types.bool;
          default = false;
          description = ''
            Open ports in the firewall for deluge web daemon
          '';
        };
      };
    };
  };

  config = lib.mkIf cfg.enable {

    services.deluge.package = lib.mkDefault (
      if lib.versionAtLeast config.system.stateVersion "20.09" then
        pkgs.deluge-2_x
      else
        # deluge-1_x is no longer packaged and this will resolve to an error
        # thanks to the alias for this name.  This is left here so that anyone
        # using NixOS older than 20.09 receives that error when they upgrade
        # and is forced to make an intentional choice to switch to deluge-2_x.
        # That might be slightly inconvenient but there is no path to
        # downgrade from 2.x to 1.x so NixOS should not automatically perform
        # this state migration.
        pkgs.deluge-1_x
    );

    # Provide a default set of `extraPackages`.
    services.deluge.extraPackages = with pkgs; [
      unzip
      gnutar
      xz
      bzip2
    ];

    systemd.tmpfiles.settings."10-deluged" =
      let
        defaultConfig = {
          inherit (cfg) user group;
          mode = "0770";
        };
      in
      {
        "${cfg.dataDir}".d = defaultConfig;
        "${cfg.dataDir}/.config".d = defaultConfig;
        "${cfg.dataDir}/.config/deluge".d = defaultConfig;
      }
      // lib.optionalAttrs (cfg.config ? download_location) {
        ${cfg.config.download_location}.d = defaultConfig;
      }
      // lib.optionalAttrs (cfg.config ? torrentfiles_location) {
        ${cfg.config.torrentfiles_location}.d = defaultConfig;
      }
      // lib.optionalAttrs (cfg.config ? move_completed_path) {
        ${cfg.config.move_completed_path}.d = defaultConfig;
      };

    systemd.services.deluged = {
      after = [ "network.target" ];
      description = "Deluge BitTorrent Daemon";
      wantedBy = [ "multi-user.target" ];
      path = [ cfg.package ] ++ cfg.extraPackages;
      serviceConfig = {
        ExecStart = ''
          ${cfg.package}/bin/deluged \
            --do-not-daemonize \
            --config ${configDir}
        '';
        # To prevent "Quit & shutdown daemon" from working; we want systemd to
        # manage it!
        Restart = "on-success";
        User = cfg.user;
        Group = cfg.group;
        UMask = "0002";
        LimitNOFILE = cfg.openFilesLimit;
      };
      preStart = preStart;
    };

    systemd.services.delugeweb = lib.mkIf cfg_web.enable {
      after = [
        "network.target"
        "deluged.service"
      ];
      requires = [ "deluged.service" ];
      description = "Deluge BitTorrent WebUI";
      wantedBy = [ "multi-user.target" ];
      path = [ cfg.package ];
      serviceConfig = {
        ExecStart = ''
          ${cfg.package}/bin/deluge-web \
            ${lib.optionalString (!isDeluge1) "--do-not-daemonize"} \
            --config ${configDir} \
            --port ${toString cfg.web.port}
        '';
        User = cfg.user;
        Group = cfg.group;
      };
    };

    networking.firewall = lib.mkMerge [
      (lib.mkIf (cfg.declarative && cfg.openFirewall && !(cfg.config.random_port or true)) {
        allowedTCPPortRanges = lib.singleton (listToRange (cfg.config.listen_ports or listenPortsDefault));
        allowedUDPPortRanges = lib.singleton (listToRange (cfg.config.listen_ports or listenPortsDefault));
      })
      (lib.mkIf (cfg.web.openFirewall) {
        allowedTCPPorts = [ cfg.web.port ];
      })
    ];

    environment.systemPackages = [ cfg.package ];

    users.users = lib.mkIf (cfg.user == "deluge") {
      deluge = {
        group = cfg.group;
        uid = config.ids.uids.deluge;
        home = cfg.dataDir;
        description = "Deluge Daemon user";
      };
    };

    users.groups = lib.mkIf (cfg.group == "deluge") {
      deluge = {
        gid = config.ids.gids.deluge;
      };
    };
  };
}
