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

let
  inherit (lib)
    attrValues
    literalExpression
    mkEnableOption
    mkIf
    mkOption
    types
    ;
  cfg = config.services.metricbeat;

  settingsFormat = pkgs.formats.yaml {};

in
{
  options = {

    services.metricbeat = {

      enable = mkEnableOption "metricbeat";

      package = mkOption {
        type = types.package;
        default = pkgs.metricbeat;
        defaultText = literalExpression "pkgs.metricbeat";
        example = literalExpression "pkgs.metricbeat7";
        description = ''
          The metricbeat package to use
        '';
      };

      modules = mkOption {
        description = ''
          Metricbeat modules are responsible for reading metrics from the various sources.

          This is like <literal>services.metricbeat.settings.metricbeat.modules</literal>,
          but structured as an attribute set. This has the benefit that multiple
          NixOS modules can contribute settings to a single metricbeat module.

          A module can be specified multiple times by choosing a different <literal>&lt;name></literal>
          for each, but setting <xref linkend="opt-services.metricbeat.modules._name_.module"/> to the same value.

          See <link xlink:href="https://www.elastic.co/guide/en/beats/metricbeat/current/metricbeat-modules.html"/>.
        '';
        default = {};
        type = types.attrsOf (types.submodule ({ name, ... }: {
          freeformType = settingsFormat.type;
          options = {
            module = mkOption {
              type = types.str;
              default = name;
              description = ''
                The name of the module.

                Look for the value after <literal>module:</literal> on the individual
                module pages linked from <link xlink:href="https://www.elastic.co/guide/en/beats/metricbeat/current/metricbeat-modules.html"/>.
              '';
            };
          };
        }));
        example = {
          system = {
            metricsets = ["cpu" "load" "memory" "network" "process" "process_summary" "uptime" "socket_summary"];
            enabled = true;
            period = "10s";
            processes = [".*"];
            cpu.metrics = ["percentages" "normalized_percentages"];
            core.metrics = ["percentages"];
          };
        };
      };

      settings = mkOption {
        type = types.submodule {
          freeformType = settingsFormat.type;
          options = {

            name = mkOption {
              type = types.str;
              default = "";
              description = ''
                Name of the beat. Defaults to the hostname.
                See <link xlink:href="https://www.elastic.co/guide/en/beats/metricbeat/current/configuration-general-options.html#_name"/>.
              '';
            };

            tags = mkOption {
              type = types.listOf types.str;
              default = [];
              description = ''
                Tags to place on the shipped metrics.
                See <link xlink:href="https://www.elastic.co/guide/en/beats/metricbeat/current/configuration-general-options.html#_tags_2"/>.
              '';
            };

            metricbeat.modules = mkOption {
              type = types.listOf settingsFormat.type;
              default = [];
              internal = true;
              description = ''
                The metric collecting modules. Use <xref linkend="opt-services.metricbeat.modules"/> instead.

                See <link xlink:href="https://www.elastic.co/guide/en/beats/metricbeat/current/metricbeat-modules.html"/>.
              '';
            };
          };
        };
        default = {};
        description = ''
          Configuration for metricbeat. See <link xlink:href="https://www.elastic.co/guide/en/beats/metricbeat/current/configuring-howto-metricbeat.html"/> for supported values.
        '';
      };

    };
  };

  config = mkIf cfg.enable {

    assertions = [
      {
        # empty modules would cause a failure at runtime
        assertion = cfg.settings.metricbeat.modules != [];
        message = "services.metricbeat: You must configure one or more modules.";
      }
    ];

    services.metricbeat.settings.metricbeat.modules = attrValues cfg.modules;

    systemd.services.metricbeat = {
      description = "metricbeat metrics shipper";
      wantedBy = [ "multi-user.target" ];
      serviceConfig = {
        ExecStart = ''
          ${cfg.package}/bin/metricbeat \
            -c ${settingsFormat.generate "metricbeat.yml" cfg.settings} \
            --path.data $STATE_DIRECTORY \
            --path.logs $LOGS_DIRECTORY \
            ;
        '';
        Restart = "always";
        DynamicUser = true;
        ProtectSystem = "strict";
        ProtectHome = "tmpfs";
        StateDirectory = "metricbeat";
        LogsDirectory = "metricbeat";
      };
    };
  };
}