{ config, lib, pkgs, ... }:
let
  cfg = config.services.scrutiny;
  # Define the settings format used for this program
  settingsFormat = pkgs.formats.yaml { };
in
{
  options = {
    services.scrutiny = {
      enable = lib.mkEnableOption "Enables the scrutiny web application.";

      package = lib.mkPackageOptionMD pkgs "scrutiny" { };

      openFirewall = lib.mkOption {
        type = lib.types.bool;
        default = false;
        description = "Open the default ports in the firewall for Scrutiny.";
      };

      influxdb.enable = lib.mkOption {
        type = lib.types.bool;
        default = true;
        description = lib.mdDoc ''
          Enables InfluxDB on the host system using the `services.influxdb2` NixOS module
          with default options.

          If you already have InfluxDB configured, or wish to connect to an external InfluxDB
          instance, disable this option.
        '';
      };

      settings = lib.mkOption {
        description = lib.mdDoc ''
          Scrutiny settings to be rendered into the configuration file.

          See https://github.com/AnalogJ/scrutiny/blob/master/example.scrutiny.yaml.
        '';
        default = { };
        type = lib.types.submodule {
          freeformType = settingsFormat.type;

          options.web.listen.port = lib.mkOption {
            type = lib.types.port;
            default = 8080;
            description = lib.mdDoc "Port for web application to listen on.";
          };

          options.web.listen.host = lib.mkOption {
            type = lib.types.str;
            default = "0.0.0.0";
            description = lib.mdDoc "Interface address for web application to bind to.";
          };

          options.web.listen.basepath = lib.mkOption {
            type = lib.types.str;
            default = "";
            example = "/scrutiny";
            description = lib.mdDoc ''
              If Scrutiny will be behind a path prefixed reverse proxy, you can override this
              value to serve Scrutiny on a subpath.
            '';
          };

          options.log.level = lib.mkOption {
            type = lib.types.enum [ "INFO" "DEBUG" ];
            default = "INFO";
            description = lib.mdDoc "Log level for Scrutiny.";
          };

          options.web.influxdb.scheme = lib.mkOption {
            type = lib.types.str;
            default = "http";
            description = lib.mdDoc "URL scheme to use when connecting to InfluxDB.";
          };

          options.web.influxdb.host = lib.mkOption {
            type = lib.types.str;
            default = "0.0.0.0";
            description = lib.mdDoc "IP or hostname of the InfluxDB instance.";
          };

          options.web.influxdb.port = lib.mkOption {
            type = lib.types.port;
            default = 8086;
            description = lib.mdDoc "The port of the InfluxDB instance.";
          };

          options.web.influxdb.tls.insecure_skip_verify = lib.mkOption {
            type = lib.types.bool;
            default = false;
            description = lib.mdDoc "Skip TLS verification when connecting to InfluxDB.";
          };

          options.web.influxdb.token = lib.mkOption {
            type = lib.types.nullOr lib.types.str;
            default = null;
            description = lib.mdDoc "Authentication token for connecting to InfluxDB.";
          };

          options.web.influxdb.org = lib.mkOption {
            type = lib.types.nullOr lib.types.str;
            default = null;
            description = lib.mdDoc "InfluxDB organisation under which to store data.";
          };

          options.web.influxdb.bucket = lib.mkOption {
            type = lib.types.nullOr lib.types.str;
            default = null;
            description = lib.mdDoc "InfluxDB bucket in which to store data.";
          };
        };
      };

      collector = {
        enable = lib.mkEnableOption "Enables the scrutiny metrics collector.";

        package = lib.mkPackageOptionMD pkgs "scrutiny-collector" { };

        schedule = lib.mkOption {
          type = lib.types.str;
          default = "*:0/15";
          description = lib.mdDoc ''
            How often to run the collector in systemd calendar format.
          '';
        };

        settings = lib.mkOption {
          description = lib.mdDoc ''
            Collector settings to be rendered into the collector configuration file.

            See https://github.com/AnalogJ/scrutiny/blob/master/example.collector.yaml.
          '';
          default = { };
          type = lib.types.submodule {
            freeformType = settingsFormat.type;

            options.host.id = lib.mkOption {
              type = lib.types.nullOr lib.types.str;
              default = null;
              description = lib.mdDoc "Host ID for identifying/labelling groups of disks";
            };

            options.api.endpoint = lib.mkOption {
              type = lib.types.str;
              default = "http://localhost:8080";
              description = lib.mdDoc "Scrutiny app API endpoint for sending metrics to.";
            };

            options.log.level = lib.mkOption {
              type = lib.types.enum [ "INFO" "DEBUG" ];
              default = "INFO";
              description = lib.mdDoc "Log level for Scrutiny collector.";
            };
          };
        };
      };
    };
  };

  config = lib.mkIf (cfg.enable || cfg.collector.enable) {
    services.influxdb2.enable = cfg.influxdb.enable;

    networking.firewall = lib.mkIf cfg.openFirewall {
      allowedTCPPorts = [ cfg.settings.web.listen.port ];
    };

    services.smartd = lib.mkIf cfg.collector.enable {
      enable = true;
      extraOptions = [
        "-A /var/log/smartd/"
        "--interval=600"
      ];
    };

    systemd = {
      services = {
        scrutiny = lib.mkIf cfg.enable {
          description = "Hard Drive S.M.A.R.T Monitoring, Historical Trends & Real World Failure Thresholds";
          wantedBy = [ "multi-user.target" ];
          after = [ "network.target" ];
          environment = {
            SCRUTINY_VERSION = "1";
            SCRUTINY_WEB_DATABASE_LOCATION = "/var/lib/scrutiny/scrutiny.db";
            SCRUTINY_WEB_SRC_FRONTEND_PATH = "${cfg.package}/share/scrutiny";
          };
          serviceConfig = {
            DynamicUser = true;
            ExecStart = "${lib.getExe cfg.package} start --config ${settingsFormat.generate "scrutiny.yaml" cfg.settings}";
            Restart = "always";
            StateDirectory = "scrutiny";
            StateDirectoryMode = "0750";
          };
        };

        scrutiny-collector = lib.mkIf cfg.collector.enable {
          description = "Scrutiny Collector Service";
          environment = {
            COLLECTOR_VERSION = "1";
            COLLECTOR_API_ENDPOINT = cfg.collector.settings.api.endpoint;
          };
          serviceConfig = {
            Type = "oneshot";
            ExecStart = "${lib.getExe cfg.collector.package} run --config ${settingsFormat.generate "scrutiny-collector.yaml" cfg.collector.settings}";
          };
        };
      };

      timers = lib.mkIf cfg.collector.enable {
        scrutiny-collector = {
          timerConfig = {
            OnCalendar = cfg.collector.schedule;
            Persistent = true;
            Unit = "scrutiny-collector.service";
          };
        };
      };
    };
  };

  meta.maintainers = [ lib.maintainers.jnsgruk ];
}