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

with lib;

let
  cfg = config.services.hedgedoc;

  # 21.03 will not be an official release - it was instead 21.05.  This
  # versionAtLeast statement remains set to 21.03 for backwards compatibility.
  # See https://github.com/NixOS/nixpkgs/pull/108899 and
  # https://github.com/NixOS/rfcs/blob/master/rfcs/0080-nixos-release-schedule.md.
  name = if versionAtLeast config.system.stateVersion "21.03"
    then "hedgedoc"
    else "codimd";

  settingsFormat = pkgs.formats.json {};

  prettyJSON = conf:
    pkgs.runCommandLocal "hedgedoc-config.json" {
      nativeBuildInputs = [ pkgs.jq ];
    } ''
      jq '{production:del(.[]|nulls)|del(.[][]?|nulls)}' \
        < ${settingsFormat.generate "hedgedoc-ugly.json" cfg.settings} \
        > $out
    '';
in
{
  imports = [
    (mkRenamedOptionModule [ "services" "codimd" ] [ "services" "hedgedoc" ])
    (mkRenamedOptionModule
      [ "services" "hedgedoc" "configuration" ] [ "services" "hedgedoc" "settings" ])
    (mkRenamedOptionModule
      [ "services" "hedgedoc" "groups" ] [ "users" "users" name "extraGroups" ])
    (mkRemovedOptionModule [ "services" "hedgedoc" "workDir" ] ''
      TODO: write paragraph
    '')
  ];

  options.services.hedgedoc = {
    enable = mkEnableOption (lib.mdDoc "the HedgeDoc Markdown Editor");
    enableUnixSocket = mkOption {
      type = types.bool;
      default = false;
      description = lib.mdDoc ''
        Sets up a socket at `/run/hedgedoc/hedgedoc.sock`.

        This socket will be part of the `hedgedoc` group,
        so if you want a webserver like nginx to be able to
        communicate with this socket, you will have to add
        the user of the webserver (nginx in this case) to
        `users.groups.hedgedoc.members`
      '';
    };

    settings = let options = {
      # host = mkOption {
      #   type = types.str;
      #   default = "localhost";
      #   description = lib.mdDoc ''
      #     Address to listen on.
      #   '';
      # };
      # port = mkOption {
      #   type = types.port;
      #   default = 3000;
      #   example = 80;
      #   description = lib.mdDoc ''
      #     Port to listen on.
      #   '';
      # };
      # allowOrigin = mkOption {
      #   type = types.listOf types.str;
      #   default = [];
      #   example = [ "localhost" "hedgedoc.org" ];
      #   description = lib.mdDoc ''
      #     List of domains to whitelist.
      #   '';
      # };
      # useSSL = mkOption {
      #   type = types.bool;
      #   default = false;
      #   description = lib.mdDoc ''
      #     Enable to use SSL server. This will also enable
      #     {option}`protocolUseSSL`.
      #   '';
      # };
      # dbURL = mkOption {
      #   type = types.nullOr types.str;
      #   default = null;
      #   example = ''
      #     postgres://user:pass@host:5432/dbname
      #   '';
      #   description = lib.mdDoc ''
      #     Specify which database to use.
      #     HedgeDoc supports mysql, postgres, sqlite and mssql.
      #     See [
      #     https://sequelize.readthedocs.io/en/v3/](https://sequelize.readthedocs.io/en/v3/) for more information.
      #     Note: This option overrides {option}`db`.
      #   '';
      # };
      # db = mkOption {
      #   type = types.attrs;
      #   default = {};
      #   example = literalExpression ''
      #     {
      #       dialect = "sqlite";
      #       storage = "/var/lib/${name}/db.${name}.sqlite";
      #     }
      #   '';
      #   description = lib.mdDoc ''
      #     Specify the configuration for sequelize.
      #     HedgeDoc supports mysql, postgres, sqlite and mssql.
      #     See [
      #     https://sequelize.readthedocs.io/en/v3/](https://sequelize.readthedocs.io/en/v3/) for more information.
      #     Note: This option overrides {option}`db`.
      #   '';
      # };
      # sslKeyPath= mkOption {
      #   type = types.nullOr types.str;
      #   default = null;
      #   example = "/var/lib/hedgedoc/hedgedoc.key";
      #   description = lib.mdDoc ''
      #     Path to the SSL key. Needed when {option}`useSSL` is enabled.
      #   '';
      # };
      # sslCertPath = mkOption {
      #   type = types.nullOr types.str;
      #   default = null;
      #   example = "/var/lib/hedgedoc/hedgedoc.crt";
      #   description = lib.mdDoc ''
      #     Path to the SSL cert. Needed when {option}`useSSL` is enabled.
      #   '';
      # };
      # sslCAPath = mkOption {
      #   type = types.listOf types.str;
      #   default = [];
      #   example = [ "/var/lib/hedgedoc/ca.crt" ];
      #   description = lib.mdDoc ''
      #     SSL ca chain. Needed when {option}`useSSL` is enabled.
      #   '';
      # };
      # dhParamPath = mkOption {
      #   type = types.nullOr types.str;
      #   default = null;
      #   example = "/var/lib/hedgedoc/dhparam.pem";
      #   description = lib.mdDoc ''
      #     Path to the SSL dh params. Needed when {option}`useSSL` is enabled.
      #   '';
      # };
      # tmpPath = mkOption {
      #   type = types.str;
      #   default = "/tmp";
      #   description = lib.mdDoc ''
      #     Path to the temp directory HedgeDoc should use.
      #     Note that {option}`serviceConfig.PrivateTmp` is enabled for
      #     the HedgeDoc systemd service by default.
      #     (Non-canonical paths are relative to HedgeDoc's base directory)
      #   '';
      # };
      # defaultNotePath = mkOption {
      #   type = types.nullOr types.str;
      #   default = "${cfg.package}/public/default.md";
      #   defaultText = literalExpression "\"\${cfg.package}/public/default.md\"";
      #   description = lib.mdDoc ''
      #     Path to the default Note file.
      #     (Non-canonical paths are relative to HedgeDoc's base directory)
      #   '';
      # };
      # docsPath = mkOption {
      #   type = types.nullOr types.str;
      #   default = "${cfg.package}/public/docs";
      #   defaultText = literalExpression "\"\${cfg.package}/public/docs\"";
      #   description = lib.mdDoc ''
      #     Path to the docs directory.
      #     (Non-canonical paths are relative to HedgeDoc's base directory)
      #   '';
      # };
      # indexPath = mkOption {
      #   type = types.nullOr types.str;
      #   default = "${cfg.package}/public/views/index.ejs";
      #   defaultText = literalExpression "\"\${cfg.package}/public/views/index.ejs\"";
      #   description = lib.mdDoc ''
      #     Path to the index template file.
      #     (Non-canonical paths are relative to HedgeDoc's base directory)
      #   '';
      # };
      # hackmdPath = mkOption {
      #   type = types.nullOr types.str;
      #   default = "${cfg.package}/public/views/hackmd.ejs";
      #   defaultText = literalExpression "\"\${cfg.package}/public/views/hackmd.ejs\"";
      #   description = lib.mdDoc ''
      #     Path to the hackmd template file.
      #     (Non-canonical paths are relative to HedgeDoc's base directory)
      #   '';
      # };
      # errorPath = mkOption {
      #   type = types.nullOr types.str;
      #   default = "${cfg.package}/public/views/error.ejs";
      #   defaultText = literalExpression "\"\${cfg.package}/public/views/error.ejs\"";
      #   description = lib.mdDoc ''
      #     Path to the error template file.
      #     (Non-canonical paths are relative to HedgeDoc's base directory)
      #   '';
      # };
      # prettyPath = mkOption {
      #   type = types.nullOr types.str;
      #   default = "${cfg.package}/public/views/pretty.ejs";
      #   defaultText = literalExpression "\"\${cfg.package}/public/views/pretty.ejs\"";
      #   description = lib.mdDoc ''
      #     Path to the pretty template file.
      #     (Non-canonical paths are relative to HedgeDoc's base directory)
      #   '';
      # };
      # slidePath = mkOption {
      #   type = types.nullOr types.str;
      #   default = "${cfg.package}/public/views/slide.hbs";
      #   defaultText = literalExpression "\"\${cfg.package}/public/views/slide.hbs\"";
      #   description = lib.mdDoc ''
      #     Path to the slide template file.
      #     (Non-canonical paths are relative to HedgeDoc's base directory)
      #   '';
      # };
      # uploadsPath = mkOption {
      #   type = types.str;
      #   default = "${cfg.workDir}/uploads";
      #   defaultText = literalExpression "\"\${cfg.workDir}/uploads\"";
      #   description = lib.mdDoc ''
      #     Path under which uploaded files are saved.
      #   '';
      # };
      # ldap = mkOption {
      #   type = types.nullOr (types.submodule {
      #     options = {
      #       providerName = mkOption {
      #         type = types.str;
      #         default = "";
      #         description = lib.mdDoc ''
      #           Optional name to be displayed at login form, indicating the LDAP provider.
      #         '';
      #       };
      #       url = mkOption {
      #         type = types.str;
      #         example = "ldap://localhost";
      #         description = lib.mdDoc ''
      #           URL of LDAP server.
      #         '';
      #       };
      #       bindDn = mkOption {
      #         type = types.str;
      #         description = lib.mdDoc ''
      #           Bind DN for LDAP access.
      #         '';
      #       };
      #       bindCredentials = mkOption {
      #         type = types.str;
      #         description = lib.mdDoc ''
      #           Bind credentials for LDAP access.
      #         '';
      #       };
      #       searchBase = mkOption {
      #         type = types.str;
      #         example = "o=users,dc=example,dc=com";
      #         description = lib.mdDoc ''
      #           LDAP directory to begin search from.
      #         '';
      #       };
      #       searchFilter = mkOption {
      #         type = types.str;
      #         example = "(uid={{username}})";
      #         description = lib.mdDoc ''
      #           LDAP filter to search with.
      #         '';
      #       };
      #       searchAttributes = mkOption {
      #         type = types.nullOr (types.listOf types.str);
      #         default = null;
      #         example = [ "displayName" "mail" ];
      #         description = lib.mdDoc ''
      #           LDAP attributes to search with.
      #         '';
      #       };
      #       userNameField = mkOption {
      #         type = types.str;
      #         default = "";
      #         description = lib.mdDoc ''
      #           LDAP field which is used as the username on HedgeDoc.
      #           By default {option}`useridField` is used.
      #         '';
      #       };
      #       useridField = mkOption {
      #         type = types.str;
      #         example = "uid";
      #         description = lib.mdDoc ''
      #           LDAP field which is a unique identifier for users on HedgeDoc.
      #         '';
      #       };
      #       tlsca = mkOption {
      #         type = types.str;
      #         default = "/etc/ssl/certs/ca-certificates.crt";
      #         example = "server-cert.pem,root.pem";
      #         description = lib.mdDoc ''
      #           Root CA for LDAP TLS in PEM format.
      #         '';
      #       };
      #     };
      #   });
      #   default = null;
      #   description = lib.mdDoc "Configure the LDAP integration.";
      # };
    }; in lib.mkOption {
      type = lib.types.submodule {
        freeformType = settingsFormat.type;
        inherit options;
      };
      description = lib.mdDoc ''
        HedgeDoc configuration, see
        <https://docs.hedgedoc.org/configuration/>
        for documentation.
      '';
    };

    environmentFile = mkOption {
      type = with types; nullOr path;
      default = null;
      example = "/var/lib/hedgedoc/hedgedoc.env";
      description = lib.mdDoc ''
        Environment file as defined in {manpage}`systemd.exec(5)`.

        Secrets may be passed to the service without adding them to the world-readable
        Nix store, by specifying placeholder variables as the option value in Nix and
        setting these variables accordingly in the environment file.

        ```
          # snippet of HedgeDoc-related config
          services.hedgedoc.settings.dbURL = "postgres://hedgedoc:\''${DB_PASSWORD}@db-host:5432/hedgedocdb";
          services.hedgedoc.settings.minio.secretKey = "$MINIO_SECRET_KEY";
        ```

        ```
          # content of the environment file
          DB_PASSWORD=verysecretdbpassword
          MINIO_SECRET_KEY=verysecretminiokey
        ```

        Note that this file needs to be available on the host on which
        `HedgeDoc` is running.
      '';
    };

    package = mkPackageOption pkgs "hedgedoc" { };
  };

  config = mkIf cfg.enable {
    users.groups."hedgedoc" = { };
    users.users.${name} = {
      description = "HedgeDoc service user";
      group = name;
      isSystemUser = true;
    };

    systemd.services.hedgedoc = {
      description = "HedgeDoc Service";
      wantedBy = [ "multi-user.target" ];
      after = [ "networking.target" ];
      preStart = ''
        ${pkgs.envsubst}/bin/envsubst \
          -o /var/lib/${name}/config.json \
          -i ${prettyJSON cfg.settings}
      '';
      serviceConfig = {
        RuntimeDirectory = [ name ];
        # WorkingDirectory = "/var/lib/${}";
        # StateDirectory = [ cfg.workDir cfg.settings.uploadsPath ];
        ReadWriteDirectories = mkIf (cfg.settings ? "uploadsPath") [ cfg.settings.uploadsPath ];
        StateDirectory = [ name ];
        ExecStart = "${cfg.package}/bin/hedgedoc";
        EnvironmentFile = mkIf (cfg.environmentFile != null) [ cfg.environmentFile ];
        Environment = [
          "CMD_CONFIG_FILE=/var/lib/${name}/config.json"
          "NODE_ENV=production"
        ];
        Restart = "always";
        # DynamicUser = true;
        User = name;
        Group = name;

        # Hardening
        CapabilityBoundingSet = "";
        LockPersonality = true;
        NoNewPrivileges = true;
        PrivateDevices = true;
        PrivateMounts = true;
        PrivateTmp = true;
        PrivateUsers = true;
        ProtectClock = true;
        ProtectHome = true;
        ProtectHostname = true;
        ProtectKernelLogs = true;
        ProtectKernelModules = true;
        ProtectKernelTunables = true;
        ProtectProc = "invisible";
        ProtectSystem = "strict";
        RemoveIPC = true;
        RestrictSUIDSGID = true;
        UMask = "0007";
        RestrictAddressFamilies = [ "AF_INET" "AF_INET6" ] ++ (lib.optional (cfg.settings.path != null) "AF_UNIX");
        SystemCallArchitectures = "native";
        SystemCallFilter = "~@clock @cpu-emulation @debug @keyring @module @mount @obsolete @raw-io @reboot @setuid @swap";
      };
    };
  };
}