package probers

import (
	"context"
	"fmt"
	"strings"

	"github.com/prometheus/client_golang/prometheus"

	"github.com/letsencrypt/boulder/cmd"
)

var (
	// Registry is the global mapping of all `Configurer` types. Types
	// are added to this mapping on import by including a call to
	// `Register` in their `init` function.
	Registry = make(map[string]Configurer)
)

// Prober is the interface for `Prober` types.
type Prober interface {
	// Name returns a name that uniquely identifies the monitor that
	// configured this `Prober`.
	Name() string

	// Kind returns a name that uniquely identifies the `Kind` of
	// `Prober`.
	Kind() string

	// Probe attempts the configured request or query. It must respect the
	// cancellation of its context.
	Probe(context.Context) error
}

// Configurer is the interface for `Configurer` types.
type Configurer interface {
	// Kind returns a name that uniquely identifies the `Kind` of
	// `Configurer`.
	Kind() string

	// UnmarshalSettings unmarshals YAML as bytes to a `Configurer`
	// object.
	UnmarshalSettings([]byte) (Configurer, error)

	// MakeProber constructs a `Prober` object from the contents of the
	// bound `Configurer` object. If the `Configurer` cannot be
	// validated, an error appropriate for end-user consumption is
	// returned instead. The map of `prometheus.Collector` objects passed to
	// MakeProber should be the same as the return value from Instrument()
	MakeProber(map[string]prometheus.Collector) (Prober, error)

	// Instrument constructs any `prometheus.Collector` objects that a prober of
	// the configured type will need to report its own metrics. A map is
	// returned containing the constructed objects, indexed by the name of the
	// prometheus metric. If no objects were constructed, nil is returned.
	Instrument() map[string]prometheus.Collector
}

// Settings is exported as a temporary receiver for the `settings` field
// of `MonConf`. `Settings` is always marshaled back to bytes and then
// unmarshalled into the `Configurer` specified by the `Kind` field of
// the `MonConf`.
type Settings map[string]any

// normalizeKind normalizes the input string by stripping spaces and
// transforming it into lowercase
func normalizeKind(kind string) string {
	return strings.Trim(strings.ToLower(kind), " ")
}

// GetConfigurer returns the probe configurer specified by name from
// `Registry`.
func GetConfigurer(kind string) (Configurer, error) {
	name := normalizeKind(kind)
	// check if exists
	if _, ok := Registry[name]; ok {
		return Registry[name], nil
	}
	return nil, fmt.Errorf("%s is not a registered Prober type", kind)
}

// Register is called by the `init` function of every `Configurer` to
// add the caller to the global `Registry` map. If the caller attempts
// to add a `Configurer` to the registry using the same name as a prior
// `Configurer` Observer will exit after logging an error.
func Register(c Configurer) {
	name := normalizeKind(c.Kind())
	// check for name collision
	if _, exists := Registry[name]; exists {
		cmd.Fail(fmt.Sprintf(
			"problem registering configurer %s: name collision", c.Kind()))
	}
	Registry[name] = c
}
