{ config, lib, ... }:

let
  inherit (lib)
    attrsToList
    concatStringsSep
    filterAttrs
    getAttr
    mapAttrs
    mapAttrs'
    mkEnableOption
    mkIf
    mkOption
    nameValuePair
    recursiveUpdate
    ;

  inherit (lib.types)
    attrs
    attrsOf
    bool
    port
    str
    submodule
    ;

  cfg = config.dgn-web;
in
{
  options.dgn-web = {
    enable = mkEnableOption "sane defaults for web services.";

    internalPorts = mkOption {
      type = attrsOf port;
      default = { };
      description = ''
        Map from the web services to their internal ports, it should avoid port clashes.
      '';
    };

    simpleProxies = mkOption {
      type = attrsOf (submodule {
        options = {
          port = mkOption {
            type = port;
            description = ''
              Port where the service will listen.
            '';
          };

          host = mkOption {
            type = str;
            description = ''
              Hostname of the service.
            '';
          };

          proxyWebsockets = mkOption {
            type = bool;
            default = false;
            description = ''
              Whether to support proxying websocket connections with HTTP/1.1.
            '';
          };

          vhostConfig = mkOption {
            type = attrs;
            default = { };
            description = ''
              Additional virtualHost settings.
            '';
          };
        };
      });
      default = { };
      description = ''
        A set of simple localhost redirections.
      '';
    };
  };

  config = mkIf cfg.enable {
    assertions = [
      (
        let
          duplicates = builtins.attrValues (
            builtins.mapAttrs (p: serv: "${p}: ${concatStringsSep ", " serv}") (
              filterAttrs (_: ls: builtins.length ls != 1) (
                builtins.foldl' (
                  rev:
                  { name, value }:
                  let
                    str = builtins.toString value;
                  in
                  rev // { ${str} = (rev.${str} or [ ]) ++ [ name ]; }
                ) { } (attrsToList cfg.internalPorts)
              )
            )
          );
        in
        {
          assertion = duplicates == [ ];
          message = ''
            Internal ports cannot be used for multiple services, the clashes are:
              ${concatStringsSep "\n  " duplicates}
          '';
        }
      )
    ];

    dgn-web.internalPorts = mapAttrs (_: getAttr "port") cfg.simpleProxies;

    services.nginx = {
      enable = true;

      virtualHosts = mapAttrs' (
        _:
        {
          host,
          port,
          proxyWebsockets,
          vhostConfig,
        }:
        nameValuePair host (
          recursiveUpdate {
            forceSSL = true;
            enableACME = true;

            locations."/" = {
              proxyPass = "http://127.0.0.1:${builtins.toString port}";
              inherit proxyWebsockets;
            };
          } vhostConfig
        )
      ) cfg.simpleProxies;

      recommendedBrotliSettings = true;
      recommendedGzipSettings = true;
      recommendedOptimisation = true;
      recommendedProxySettings = true;
      recommendedTlsSettings = true;
      recommendedZstdSettings = true;
    };

    networking.firewall.allowedTCPPorts = [
      80
      443
    ];
  };
}