// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0

package healthcheck

import (
	"fmt"
	"time"

	"github.com/hashicorp/vault/sdk/logical"

	"github.com/hashicorp/go-secure-stdlib/parseutil"
)

type EnableAutoTidy struct {
	Enabled            bool
	UnsupportedVersion bool

	IntervalDurationCritical time.Duration
	IntervalDurationWarning  time.Duration
	PauseDurationCritical    time.Duration
	PauseDurationWarning     time.Duration

	TidyConfig *PathFetch
}

func NewEnableAutoTidyCheck() Check {
	return &EnableAutoTidy{}
}

func (h *EnableAutoTidy) Name() string {
	return "enable_auto_tidy"
}

func (h *EnableAutoTidy) IsEnabled() bool {
	return h.Enabled
}

func (h *EnableAutoTidy) DefaultConfig() map[string]interface{} {
	return map[string]interface{}{
		"interval_duration_critical": "7d",
		"interval_duration_warning":  "2d",
		"pause_duration_critical":    "1s",
		"pause_duration_warning":     "200ms",
	}
}

func (h *EnableAutoTidy) fromConfig(config map[string]interface{}, param string) (time.Duration, error) {
	value, err := parseutil.ParseDurationSecond(config[param])
	if err != nil {
		return time.Duration(0), fmt.Errorf("failed to parse parameter %v.%v=%v: %w", h.Name(), param, config[param], err)
	}

	return value, nil
}

func (h *EnableAutoTidy) LoadConfig(config map[string]interface{}) error {
	var err error

	h.IntervalDurationCritical, err = h.fromConfig(config, "interval_duration_critical")
	if err != nil {
		return err
	}

	h.IntervalDurationWarning, err = h.fromConfig(config, "interval_duration_warning")
	if err != nil {
		return err
	}

	h.PauseDurationCritical, err = h.fromConfig(config, "pause_duration_critical")
	if err != nil {
		return err
	}

	h.PauseDurationWarning, err = h.fromConfig(config, "pause_duration_warning")
	if err != nil {
		return err
	}

	enabled, err := parseutil.ParseBool(config["enabled"])
	if err != nil {
		return fmt.Errorf("error parsing %v.enabled: %w", h.Name(), err)
	}
	h.Enabled = enabled

	return nil
}

func (h *EnableAutoTidy) FetchResources(e *Executor) error {
	var err error
	h.TidyConfig, err = e.FetchIfNotFetched(logical.ReadOperation, "/{{mount}}/config/auto-tidy")
	if err != nil {
		return err
	}

	if h.TidyConfig.IsUnsupportedPathError() {
		h.UnsupportedVersion = true
	}

	return nil
}

func (h *EnableAutoTidy) Evaluate(e *Executor) (results []*Result, err error) {
	if h.UnsupportedVersion {
		ret := Result{
			Status:   ResultInvalidVersion,
			Endpoint: "/{{mount}}/config/auto-tidy",
			Message:  "This health check requires Vault 1.12+, but an earlier version of Vault Server was contacted, preventing this health check from running.",
		}
		return []*Result{&ret}, nil
	}

	if h.TidyConfig == nil {
		return
	}

	if h.TidyConfig.IsSecretPermissionsError() {
		ret := Result{
			Status:   ResultInsufficientPermissions,
			Endpoint: "/{{mount}}/config/auto-tidy",
			Message:  "This prevents the health check from functioning at all, as it cannot .",
		}

		if e.Client.Token() == "" {
			ret.Message = "No token available so unable read authenticated auto-tidy configuration for this mount. " + ret.Message
		} else {
			ret.Message = "This token lacks permission to read the auto-tidy configuration for this mount. " + ret.Message
		}

		return []*Result{&ret}, nil
	}

	isEnabled := h.TidyConfig.Secret.Data["enabled"].(bool)
	intervalDuration, err := parseutil.ParseDurationSecond(h.TidyConfig.Secret.Data["interval_duration"])
	if err != nil {
		return nil, fmt.Errorf("error parsing API response from server for interval_duration: %w", err)
	}

	pauseDuration, err := parseutil.ParseDurationSecond(h.TidyConfig.Secret.Data["pause_duration"])
	if err != nil {
		return nil, fmt.Errorf("error parsing API response from server for pause_duration: %w", err)
	}

	if !isEnabled {
		ret := Result{
			Status:   ResultInformational,
			Endpoint: "/{{mount}}/config/auto-tidy",
			Message:  "Auto-tidy is currently disabled; consider enabling auto-tidy to execute tidy operations periodically. This helps the health and performance of a mount.",
		}
		results = append(results, &ret)
	} else {
		baseMsg := "Auto-tidy is configured with too long of a value for %v (%v); this could impact performance as tidies run too infrequently or take too long to execute."

		if intervalDuration >= h.IntervalDurationCritical {
			ret := Result{
				Status:   ResultCritical,
				Endpoint: "/{{mount}}/config/auto-tidy",
				Message:  fmt.Sprintf(baseMsg, "interval_duration", intervalDuration),
			}
			results = append(results, &ret)
		} else if intervalDuration >= h.IntervalDurationWarning {
			ret := Result{
				Status:   ResultWarning,
				Endpoint: "/{{mount}}/config/auto-tidy",
				Message:  fmt.Sprintf(baseMsg, "interval_duration", intervalDuration),
			}
			results = append(results, &ret)
		}

		if pauseDuration >= h.PauseDurationCritical {
			ret := Result{
				Status:   ResultCritical,
				Endpoint: "/{{mount}}/config/auto-tidy",
				Message:  fmt.Sprintf(baseMsg, "pause_duration", pauseDuration),
			}
			results = append(results, &ret)
		} else if pauseDuration >= h.PauseDurationWarning {
			ret := Result{
				Status:   ResultWarning,
				Endpoint: "/{{mount}}/config/auto-tidy",
				Message:  fmt.Sprintf(baseMsg, "pause_duration", pauseDuration),
			}
			results = append(results, &ret)
		}

		if len(results) == 0 {
			ret := Result{
				Status:   ResultOK,
				Endpoint: "/{{mount}}/config/auto-tidy",
				Message:  "Auto-tidy is enabled and configured appropriately.",
			}
			results = append(results, &ret)
		}
	}

	return
}
