# SPDX-FileCopyrightText: 2022 Luke Granger-Brown <depot@lukegb.com>
#
# SPDX-License-Identifier: Apache-2.0

{ pkgs, config, depot, lib, ... }:
let
  inherit (lib) mkOption types mkBefore optionalAttrs mkDefault mkIf mapAttrsToList mkAfter;

  acmeCertificates = builtins.attrValues config.my.vault.acmeCertificates;

  # Work out where we're being asked to write things, and which groups, so we can correctly get permissions.
  fullchainPath = c: pathFor c "fullchain.pem";
  chainPath = c: pathFor c "chain.pem";
  keyPath = c: pathFor c "privkey.pem";
  pathFor = c: suffix: "/var/lib/acme/${c.name}/${suffix}";

  isNginx = c: builtins.length c.nginxVirtualHosts > 0;
  defaultGroup = c: if isNull c.group then if isNginx c then "nginx" else "acme" else c.group;
  groupOrDefault = p: c: if isNull p then defaultGroup c else p;

  allRestartableUnits = builtins.concatMap (c: c.reloadOrRestartUnits) acmeCertificates;
  allGroups = lib.unique (map (c: c.group) acmeCertificates);
in
{
  imports = [
    ./secretsmgr.nix
  ];

  options.my.vault.acmeCertificates = mkOption {
    default = {};
    type = with types; attrsOf (submodule ({ name, config, ... }: {
      options = let
        isNginx = builtins.length config.nginxVirtualHosts > 0;
      in {
        name = mkOption {
          type = str;
          default = name;
          description = "Path to put the certificate.";
        };

        nginxVirtualHosts = mkOption {
          type = listOf str;
          default = [];
          description = "List of nginx virtual hosts to apply SSL to.";
        };
        group = mkOption {
          type = str;
          default = if isNginx then "nginx" else "acme";
          description = "Owner group to set for the ${what}. If null, taken from parent.";
        };

        role = mkOption { 
          type = str;
          default = "letsencrypt-cloudflare";
          description = "Which role to use for certificate issuance.";
        };

        hostnames = mkOption {
          type = listOf str;
          description = "List of hostnames to issue certificate for.";
        };

        reloadOrRestartUnits = mkOption {
          type = listOf str;
          default = lib.optional isNginx "nginx.service";
          description = "List of systemd units to reload/restart after obtaining a new certificate.";
        };
      };
    }));
  };

  config = mkIf config.my.vault.secretsmgr.acmeCertificates.enable {
    services.nginx = optionalAttrs config.my.vault.enable {
      virtualHosts = builtins.listToAttrs (builtins.concatMap (certData: let
        fullchain = fullchainPath certData;
        chain     = chainPath certData;
        key       = keyPath certData;
      in map (hostName: lib.nameValuePair hostName {
        sslCertificate        = mkDefault (fullchainPath certData);
        sslCertificateKey     = mkDefault (keyPath certData);
        sslTrustedCertificate = mkDefault (chainPath certData);
      }) certData.nginxVirtualHosts) acmeCertificates);
    };

    my.vault.secretsmgr.groups = mkAfter allGroups;
    my.vault.secretsmgr.restartableUnits = mkAfter allRestartableUnits;
    my.vault.secretsmgr.acmeCertificates.config = mapAttrsToList (_: c: {
      inherit (c) group hostnames role;
      output_name = c.name;
      units_to_reload_or_restart = c.reloadOrRestartUnits;
    }) config.my.vault.acmeCertificates;
  };
}