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

with lib;

let

  inherit (pkgs) nntp-proxy;

  cfg = config.services.nntp-proxy;

  configBool = b: if b then "TRUE" else "FALSE";

  confFile = pkgs.writeText "nntp-proxy.conf" ''
    nntp_server:
    {
      # NNTP Server host and port address
      server = "${cfg.upstreamServer}";
      port = ${toString cfg.upstreamPort};
      # NNTP username
      username = "${cfg.upstreamUser}";
      # NNTP password in clear text
      password = "${cfg.upstreamPassword}";
      # Maximum number of connections allowed by the NNTP
      max_connections = ${toString cfg.upstreamMaxConnections};
    };

    proxy:
    {
      # Local address and port to bind to
      bind_ip = "${cfg.listenAddress}";
      bind_port = ${toString cfg.port};

      # SSL key and cert file
      ssl_key = "${cfg.sslKey}";
      ssl_cert = "${cfg.sslCert}";

      # prohibit users from posting
      prohibit_posting = ${configBool cfg.prohibitPosting};
      # Verbose levels: ERROR, WARNING, NOTICE, INFO, DEBUG
      verbose = "${toUpper cfg.verbosity}";
      # Password is made with: 'mkpasswd -m sha-512 <password>'
      users = (${
        concatStringsSep ",\n" (
          mapAttrsToList (username: userConfig: ''
            {
                username = "${username}";
                password = "${userConfig.passwordHash}";
                max_connections = ${toString userConfig.maxConnections};
            }
          '') cfg.users
        )
      });
    };
  '';

in

{

  ###### interface

  options = {

    services.nntp-proxy = {
      enable = mkEnableOption "NNTP-Proxy";

      upstreamServer = mkOption {
        type = types.str;
        default = "";
        example = "ssl-eu.astraweb.com";
        description = ''
          Upstream server address
        '';
      };

      upstreamPort = mkOption {
        type = types.port;
        default = 563;
        description = ''
          Upstream server port
        '';
      };

      upstreamMaxConnections = mkOption {
        type = types.int;
        default = 20;
        description = ''
          Upstream server maximum allowed concurrent connections
        '';
      };

      upstreamUser = mkOption {
        type = types.str;
        default = "";
        description = ''
          Upstream server username
        '';
      };

      upstreamPassword = mkOption {
        type = types.str;
        default = "";
        description = ''
          Upstream server password
        '';
      };

      listenAddress = mkOption {
        type = types.str;
        default = "127.0.0.1";
        example = "[::]";
        description = ''
          Proxy listen address (IPv6 literal addresses need to be enclosed in "[" and "]" characters)
        '';
      };

      port = mkOption {
        type = types.port;
        default = 5555;
        description = ''
          Proxy listen port
        '';
      };

      sslKey = mkOption {
        type = types.str;
        default = "key.pem";
        example = "/path/to/your/key.file";
        description = ''
          Proxy ssl key path
        '';
      };

      sslCert = mkOption {
        type = types.str;
        default = "cert.pem";
        example = "/path/to/your/cert.file";
        description = ''
          Proxy ssl certificate path
        '';
      };

      prohibitPosting = mkOption {
        type = types.bool;
        default = true;
        description = ''
          Whether to prohibit posting to the upstream server
        '';
      };

      verbosity = mkOption {
        type = types.enum [
          "error"
          "warning"
          "notice"
          "info"
          "debug"
        ];
        default = "info";
        example = "error";
        description = ''
          Verbosity level
        '';
      };

      users = mkOption {
        type = types.attrsOf (
          types.submodule {
            options = {
              username = mkOption {
                type = types.str;
                description = ''
                  Username
                '';
              };

              passwordHash = mkOption {
                type = types.str;
                example = "$6$GtzE7FrpE$wwuVgFYU.TZH4Rz.Snjxk9XGua89IeVwPQ/fEUD8eujr40q5Y021yhn0aNcsQ2Ifw.BLclyzvzgegopgKcneL0";
                description = ''
                  SHA-512 password hash (can be generated by
                  `mkpasswd -m sha-512 <password>`)
                '';
              };

              maxConnections = mkOption {
                type = types.int;
                default = 1;
                description = ''
                  Maximum number of concurrent connections to the proxy for this user
                '';
              };
            };
          }
        );
        description = ''
          NNTP-Proxy user configuration
        '';

        default = { };
        example = literalExpression ''
          {
            "user1" = {
              passwordHash = "$6$1l0t5Kn2Dk$appzivc./9l/kjq57eg5UCsBKlcfyCr0zNWYNerKoPsI1d7eAwiT0SVsOVx/CTgaBNT/u4fi2vN.iGlPfv1ek0";
              maxConnections = 5;
            };
            "anotheruser" = {
              passwordHash = "$6$6lwEsWB.TmsS$W7m1riUx4QrA8pKJz8hvff0dnF1NwtZXgdjmGqA1Dx2MDPj07tI9GNcb0SWlMglE.2/hBgynDdAd/XqqtRqVQ0";
              maxConnections = 7;
            };
          }
        '';
      };
    };

  };

  ###### implementation

  config = mkIf cfg.enable {

    users.users.nntp-proxy = {
      isSystemUser = true;
      group = "nntp-proxy";
      description = "NNTP-Proxy daemon user";
    };
    users.groups.nntp-proxy = { };

    systemd.services.nntp-proxy = {
      description = "NNTP proxy";
      after = [
        "network.target"
        "nss-lookup.target"
      ];
      wantedBy = [ "multi-user.target" ];
      serviceConfig = {
        User = "nntp-proxy";
      };
      serviceConfig.ExecStart = "${nntp-proxy}/bin/nntp-proxy ${confFile}";
      preStart = ''
        if [ ! \( -f ${cfg.sslCert} -a -f ${cfg.sslKey} \) ]; then
          ${pkgs.openssl.bin}/bin/openssl req -subj '/CN=AutoGeneratedCert/O=NixOS Service/C=US' \
          -new -newkey rsa:2048 -days 365 -nodes -x509 -keyout ${cfg.sslKey} -out ${cfg.sslCert};
        fi
      '';
    };

  };

}
