{
  config,
  pkgs,
  lib,
  utils,
  ...
}:
let
  cfg = config.services.qbittorrent;
  inherit (builtins) concatStringsSep isAttrs isString;
  inherit (lib)
    literalExpression
    getExe
    mkEnableOption
    mkOption
    mkPackageOption
    mkIf
    maintainers
    escape
    replaceString
    collect
    mapAttrsRecursive
    optionals
    ;
  inherit (lib.types)
    str
    port
    path
    nullOr
    listOf
    attrsOf
    anything
    submodule
    ;
  inherit (lib.generators) toINI mkKeyValueDefault mkValueStringDefault;
  gendeepINI = toINI {
    mkKeyValue =
      let
        sep = "=";
      in
      k: v:
      if isAttrs v then
        concatStringsSep "\n" (
          collect isString (
            mapAttrsRecursive (
              path: value:
              "${escape [ sep ] (concatStringsSep "\\" ([ k ] ++ path))}${sep}${
                replaceString "\n" "\\n" (mkValueStringDefault { } value)
              }"
            ) v
          )
        )
      else
        mkKeyValueDefault { } sep k v;
  };
  configFile = pkgs.writeText "qBittorrent.conf" (gendeepINI cfg.serverConfig);
in
{
  options.services.qbittorrent = {
    enable = mkEnableOption "qbittorrent, BitTorrent client";

    package = mkPackageOption pkgs "qbittorrent-nox" { };

    user = mkOption {
      type = str;
      default = "qbittorrent";
      description = "User account under which qbittorrent runs.";
    };

    group = mkOption {
      type = str;
      default = "qbittorrent";
      description = "Group under which qbittorrent runs.";
    };

    profileDir = mkOption {
      type = path;
      default = "/var/lib/qBittorrent";
      description = ''
        The path passed to qbittorrent via --profile.
        Please note that the resulting systemd service will be run with some very strict security controls,
        such as `ProtectHome=yes`, `PrivateUsers=true`, and `ProtectSystem=full` among others, so you cannot specify
        a profileDir in your home directory. See [this bug report](https://github.com/NixOS/nixpkgs/issues/514206) for further info.
      '';
    };

    openFirewall = mkEnableOption "opening both the webuiPort and torrentPort over TCP in the firewall";

    webuiPort = mkOption {
      default = 8080;
      type = nullOr port;
      description = "the port passed to qbittorrent via `--webui-port`";
    };

    torrentingPort = mkOption {
      default = null;
      type = nullOr port;
      description = "the port passed to qbittorrent via `--torrenting-port`";
    };

    serverConfig = mkOption {
      default = { };
      type = submodule {
        freeformType = attrsOf (attrsOf anything);
      };
      description = ''
        Free-form settings mapped to the {file}`qBittorrent.conf` file in the profile.
        Refer to [Explanation-of-Options-in-qBittorrent](https://github.com/qbittorrent/qBittorrent/wiki/Explanation-of-Options-in-qBittorrent).
        The Password_PBKDF2 format is oddly unique, you will likely want to use [this tool](https://codeberg.org/feathecutie/qbittorrent_password) to generate the format.
        Alternatively you can run qBittorrent independently first and use its webUI to generate the format.

        Optionally an alternative webUI can be easily set. VueTorrent for example:
        ```nix
        {
          Preferences = {
            WebUI = {
              AlternativeUIEnabled = true;
              RootFolder = "''${pkgs.vuetorrent}/share/vuetorrent";
            };
          };
        }
        ];
        ```
      '';
      example = literalExpression ''
        {
          LegalNotice.Accepted = true;
          Preferences = {
            WebUI = {
              Username = "user";
              Password_PBKDF2 = "generated ByteArray.";
            };
            General.Locale = "en";
          };
        }
      '';
    };

    extraArgs = mkOption {
      type = listOf str;
      default = [ ];
      description = ''
        Extra arguments passed to qbittorrent. See `qbittorrent -h`, or the [source code](https://github.com/qbittorrent/qBittorrent/blob/master/src/app/cmdoptions.cpp), for the available arguments.
      '';
      example = [
        "--confirm-legal-notice"
      ];
    };
  };
  config = mkIf cfg.enable {
    systemd = {
      tmpfiles.settings = {
        qbittorrent = {
          "${cfg.profileDir}/qBittorrent/"."d" = {
            mode = "755";
            inherit (cfg) user group;
          };
          "${cfg.profileDir}/qBittorrent/config/"."d" = {
            mode = "755";
            inherit (cfg) user group;
          };
        };
      };
      services.qbittorrent = {
        description = "qbittorrent BitTorrent client";
        wants = [ "network-online.target" ];
        after = [
          "local-fs.target"
          "network-online.target"
          "nss-lookup.target"
        ];
        wantedBy = [ "multi-user.target" ];
        restartTriggers = optionals (cfg.serverConfig != { }) [ configFile ];

        serviceConfig = {
          Type = "simple";
          User = cfg.user;
          Group = cfg.group;

          # the config file has to be writable, so we have to do this weird dance
          ExecStartPre = lib.mkIf (cfg.serverConfig != { }) ''
            ${pkgs.coreutils}/bin/install -Dm600 ${configFile} "${cfg.profileDir}/qBittorrent/config/qBittorrent.conf"
          '';

          ExecStart = utils.escapeSystemdExecArgs (
            [
              (getExe cfg.package)
              "--profile=${cfg.profileDir}"
            ]
            ++ optionals (cfg.webuiPort != null) [ "--webui-port=${toString cfg.webuiPort}" ]
            ++ optionals (cfg.torrentingPort != null) [ "--torrenting-port=${toString cfg.torrentingPort}" ]
            ++ cfg.extraArgs
          );
          TimeoutStopSec = 1800;

          # https://github.com/qbittorrent/qBittorrent/pull/6806#discussion_r121478661
          PrivateTmp = false;

          PrivateNetwork = false;
          RemoveIPC = true;
          NoNewPrivileges = true;
          PrivateDevices = true;
          PrivateUsers = true;
          ProtectHome = "yes";
          ProtectProc = "invisible";
          ProcSubset = "pid";
          ProtectSystem = "full";
          ProtectClock = true;
          ProtectHostname = true;
          ProtectKernelLogs = true;
          ProtectKernelModules = true;
          ProtectKernelTunables = true;
          ProtectControlGroups = true;
          RestrictAddressFamilies = [
            "AF_INET"
            "AF_INET6"
            "AF_NETLINK"
          ];
          RestrictNamespaces = true;
          RestrictRealtime = true;
          RestrictSUIDSGID = true;
          LockPersonality = true;
          MemoryDenyWriteExecute = true;
          SystemCallArchitectures = "native";
          CapabilityBoundingSet = "";
          SystemCallFilter = [ "@system-service" ];
        };
      };
    };

    users = {
      users = mkIf (cfg.user == "qbittorrent") {
        qbittorrent = {
          inherit (cfg) group;
          isSystemUser = true;
        };
      };
      groups = mkIf (cfg.group == "qbittorrent") { qbittorrent = { }; };
    };

    networking.firewall.allowedTCPPorts = mkIf cfg.openFirewall (
      optionals (cfg.webuiPort != null) [ cfg.webuiPort ]
      ++ optionals (cfg.torrentingPort != null) [ cfg.torrentingPort ]
    );
  };
  meta.maintainers = with maintainers; [
    fsnkty
    undefined-landmark
  ];
}
