{ 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
      '';
    };

  };

}
