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

let
  cfg = config.shb.nginx;

  contracts = pkgs.callPackage ../contracts {};

  fqdn = c: "${c.subdomain}.${c.domain}";

  vhostConfig = lib.types.submodule {
    options = {
      subdomain = lib.mkOption {
        type = lib.types.str;
        description = "Subdomain which must be protected.";
        example = "subdomain";
      };

      domain = lib.mkOption {
        type = lib.types.str;
        description = "Domain of the subdomain.";
        example = "mydomain.com";
      };

      ssl = lib.mkOption {
        description = "Path to SSL files";
        type = lib.types.nullOr contracts.ssl.certs;
        default = null;
      };

      upstream = lib.mkOption {
        type = lib.types.str;
        description = "Upstream url to be protected.";
        example = "http://127.0.0.1:1234";
      };

      authEndpoint = lib.mkOption {
        type = lib.types.nullOr lib.types.str;
        description = "Optional auth endpoint for SSO.";
        default = null;
        example = "https://authelia.example.com";
      };

      autheliaRules = lib.mkOption {
        type = lib.types.listOf (lib.types.attrsOf lib.types.anything);
        default = [];
        description = "Authelia rule configuration";
        example = lib.literalExpression ''[{
        policy = "two_factor";
        subject = ["group:service_user"];
        }]'';
      };
    };
  };
in
{
  options.shb.nginx = {
    accessLog = lib.mkOption {
      type = lib.types.bool;
      description = "Log all requests";
      default = false;
      example = true;
    };

    debugLog = lib.mkOption {
      type = lib.types.bool;
      description = "Verbose debug of internal. This will print what servers were matched and why.";
      default = false;
      example = true;
    };

    vhosts = lib.mkOption {
      description = "Endpoints to be protected by authelia.";
      type = lib.types.listOf vhostConfig;
      default = [];
    };
  };

  config = {
    networking.firewall.allowedTCPPorts = [ 80 443 ];

    services.nginx.enable = true;
    services.nginx.logError = lib.mkIf cfg.debugLog "stderr warn";
    services.nginx.appendHttpConfig = lib.mkIf cfg.accessLog ''
        log_format apm
          '{'
          '"remote_addr":"$remote_addr",'
          '"remote_user":"$remote_user",'
          '"time_local":"$time_local",'
          '"request":"$request",'
          '"request_length":"$request_length",'
          '"server_name":"$server_name",'
          '"status":"$status",'
          '"bytes_sent":"$bytes_sent",'
          '"body_bytes_sent":"$body_bytes_sent",'
          '"referrer":"$http_referrer",'
          '"user_agent":"$http_user_agent",'
          '"gzip_ration":"$gzip_ratio",'
          '"post":"$request_body",'
          '"upstream_addr":"$upstream_addr",'
          '"upstream_status":"$upstream_status",'
          '"request_time":"$request_time",'
          '"upstream_response_time":"$upstream_response_time",'
          '"upstream_connect_time":"$upstream_connect_time",'
          '"upstream_header_time":"$upstream_header_time"'
          '}';

        access_log syslog:server=unix:/dev/log apm;
      '';

    services.nginx.virtualHosts =
      let
        vhostCfg = c: {
          ${fqdn c} = {
            forceSSL = !(isNull c.ssl);
            sslCertificate = lib.mkIf (!(isNull c.ssl)) c.ssl.paths.cert;
            sslCertificateKey = lib.mkIf (!(isNull c.ssl)) c.ssl.paths.key;

            # Taken from https://github.com/authelia/authelia/issues/178
            locations."/".extraConfig = ''
              add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
              add_header X-Content-Type-Options nosniff;
              add_header X-Frame-Options "SAMEORIGIN";
              add_header X-XSS-Protection "1; mode=block";
              add_header X-Robots-Tag "noindex, nofollow, nosnippet, noarchive";
              add_header X-Download-Options noopen;
              add_header X-Permitted-Cross-Domain-Policies none;

              proxy_set_header Host $host;
              proxy_set_header X-Real-IP $remote_addr;
              proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
              proxy_set_header X-Forwarded-Proto $scheme;
              proxy_http_version 1.1;
              proxy_set_header Upgrade $http_upgrade;
              proxy_set_header Connection "upgrade";
              proxy_cache_bypass $http_upgrade;

              proxy_pass ${c.upstream};
            ''
            + lib.optionalString (c.authEndpoint != null) ''
              auth_request /authelia;
              auth_request_set $user $upstream_http_remote_user;
              auth_request_set $groups $upstream_http_remote_groups;
              proxy_set_header X-Forwarded-User $user;
              proxy_set_header X-Forwarded-Groups $groups;
              # TODO: Are those needed?
              # auth_request_set $name $upstream_http_remote_name;
              # auth_request_set $email $upstream_http_remote_email;
              # proxy_set_header Remote-Name $name;
              # proxy_set_header Remote-Email $email;
              # TODO: Would be nice to have this working, I think.
              # set $new_cookie $http_cookie;
              # if ($http_cookie ~ "(.*)(?:^|;)\s*example\.com\.session\.id=[^;]+(.*)") {
              #     set $new_cookie $1$2;
              # }
              # proxy_set_header Cookie $new_cookie;

              auth_request_set $redirect $scheme://$http_host$request_uri;
              error_page 401 =302 ${c.authEndpoint}?rd=$redirect;
              error_page 403 = ${c.authEndpoint}/error/403;
            '';

            # Virtual endpoint created by nginx to forward auth requests.
            locations."/authelia".extraConfig = lib.mkIf (!(isNull c.authEndpoint)) ''
              internal;
              proxy_pass ${c.authEndpoint}/api/verify;

              proxy_set_header X-Forwarded-Host $host;
              proxy_set_header X-Original-URI $request_uri;
              proxy_set_header X-Original-URL $scheme://$host$request_uri;
              proxy_set_header X-Forwarded-For $remote_addr;
              proxy_set_header X-Forwarded-Proto $scheme;
              proxy_set_header Content-Length "";
              proxy_pass_request_body off;
              # TODO: Would be nice to be able to enable this.
              # proxy_ssl_verify on;
              # proxy_ssl_trusted_certificate "/etc/ssl/certs/DST_Root_CA_X3.pem";
              proxy_ssl_protocols TLSv1.2;
              proxy_ssl_ciphers 'EECDH+AESGCM:EDH+AESGCM:AES256+EECDH:AES256+EDH';
              proxy_ssl_verify_depth 2;
              proxy_ssl_server_name on;
            '';
          };
        };
      in
        lib.mkMerge (map vhostCfg cfg.vhosts);

    shb.authelia.rules =
      let
        authConfig = c: map (r: r // { domain = fqdn c; }) c.autheliaRules;
      in
        lib.flatten (map authConfig cfg.vhosts);

    security.acme.defaults.reloadServices = [
      "nginx.service"
    ];
  };
}
