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

with import <stockholm/lib>;
let
  cfg = config.krebs.nginx;

  out = {
    options.krebs.nginx = api;
    config = lib.mkIf cfg.enable imp;
  };

  api = {
    enable = mkEnableOption "krebs.nginx";

    default404 = mkOption {
      type = types.bool;
      default = true;
      description = ''
        By default all requests not directed to an explicit hostname are
        replied with a 404 error to avoid accidental exposition of nginx
        services.

        Set this value to `false` to disable this behavior - you will then be
        able to configure a new `default_server` in the listen address entries
        again.
      '';
    };

    servers = mkOption {
      type = types.attrsOf (types.submodule {
        options = {
          server-names = mkOption {
            type = with types; listOf str;
            default =
              [config.krebs.build.host.name] ++
              concatMap (getAttr "aliases")
                        (attrValues config.krebs.build.host.nets);
          };
          listen = mkOption {
            type = with types; either str (listOf str);
            default = "80";
            apply = x:
              if typeOf x != "list"
                then [x]
                else x;
          };
          locations = mkOption {
            type = with types; listOf (attrsOf str);
            default = [];
          };
          extraConfig = mkOption {
            type = with types; string;
            default = "";
          };
          ssl = mkOption {
            type = with types; submodule ({ config, ... }: {
              options = {
                enable = mkEnableOption "ssl";
                acmeEnable = mkOption {
                  type = bool;
                  apply = x:
                    if x && config.enable
                      #conflicts because of certificate/certificate_key location
                      then throw "can't use ssl.enable and ssl.acmeEnable together"
                      else x;
                  default = false;
                  description = ''
                    enables automatical generation of lets-encrypt certificates and setting them as certificate
                    conflicts with ssl.enable
                  '';
                };
                certificate = mkOption {
                  type = str;
                };
                certificate_key = mkOption {
                  type = str;
                };
                #TODO: check for valid cipher
                ciphers = mkOption {
                  type = str;
                  default = "AES128+EECDH:AES128+EDH";
                };
                prefer_server_ciphers = mkOption {
                  type = bool;
                  default = true;
                };
                force_encryption = mkOption {
                  type = bool;
                  default = false;
                  description = ''
                    redirect all `http` traffic to the same domain but with ssl
                    protocol.
                  '';
                };
                protocols = mkOption {
                  type = listOf (enum [ "SSLv2" "SSLv3" "TLSv1" "TLSv1.1" "TLSv1.2" ]);
                  default = [ "TLSv1.1" "TLSv1.2" ];

                };
              };
            });
            default = {};
          };
        };
      });
      default = {};
    };
  };

  imp = {
    security.acme.certs = mapAttrs (_: to-acme) (filterAttrs (_: server: server.ssl.acmeEnable) cfg.servers);
    services.nginx = {
      enable = true;
      httpConfig = ''
        default_type      application/octet-stream;
        sendfile          on;
        keepalive_timeout 65;
        gzip              on;

        ${optionalString cfg.default404 ''
          server {
            listen 80 default_server;
            server_name _;
            return 404;
          }''}

        ${concatStrings (mapAttrsToList (_: to-server) cfg.servers)}
      '';
    };
  };

  to-acme = { server-names, ssl, ... }:
    optionalAttrs ssl.acmeEnable {
      email = "lassulus@gmail.com";
      webroot = "${config.security.acme.directory}/${head server-names}";
    };

  to-location = { name, value }: ''
    location ${name} {
      ${indent value}
    }
  '';

  to-server = { server-names, listen, locations, extraConfig, ssl, ... }: let
    domain = head server-names;
    acmeLocation = optionalAttrs ssl.acmeEnable (nameValuePair "/.well-known/acme-challenge" ''
      root ${config.security.acme.certs.${domain}.webroot};
    '');
  in ''
    server {
      server_name ${toString (unique server-names)};
      ${concatMapStringsSep "\n" (x: indent "listen ${x};") listen}
      ${optionalString ssl.enable (indent ''
        ${optionalString ssl.force_encryption ''
          if ($scheme = http){
            return 301 https://$server_name$request_uri;
          }
        ''}
        listen 443 ssl;
        ssl_certificate ${ssl.certificate};
        ssl_certificate_key ${ssl.certificate_key};
        ${optionalString ssl.prefer_server_ciphers ''
          ssl_prefer_server_ciphers On;
        ''}
        ssl_ciphers ${ssl.ciphers};
        ssl_protocols ${toString ssl.protocols};
      '')}
      ${optionalString ssl.acmeEnable (indent ''
        ${optionalString ssl.force_encryption ''
          if ($scheme = http){
            return 301 https://$server_name$request_uri;
          }
        ''}
        listen 443 ssl;
        ssl_certificate ${config.security.acme.directory}/${domain}/fullchain.pem;
        ssl_certificate_key ${config.security.acme.directory}/${domain}/key.pem;
        ${optionalString ssl.prefer_server_ciphers ''
          ssl_prefer_server_ciphers On;
        ''}
        ssl_ciphers ${ssl.ciphers};
        ssl_protocols ${toString ssl.protocols};
      '')}
      ${indent extraConfig}
      ${optionalString ssl.acmeEnable (indent (to-location acmeLocation))}
      ${indent (concatMapStrings to-location locations)}
    }
  '';

in
out