/*
** Copyright (C) 2001-2024 Zabbix SIA
**
** Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
** documentation files (the "Software"), to deal in the Software without restriction, including without limitation the
** rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to
** permit persons to whom the Software is furnished to do so, subject to the following conditions:
**
** The above copyright notice and this permission notice shall be included in all copies or substantial portions
** of the Software.
**
** THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
** WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
** COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
** TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
** SOFTWARE.
**/

package plugin

import (
	"context"
	"time"

	"golang.zabbix.com/plugin/example/plugin/handlers"
	"golang.zabbix.com/plugin/example/plugin/params"
	"golang.zabbix.com/sdk/errs"
	"golang.zabbix.com/sdk/metric"
	"golang.zabbix.com/sdk/plugin"
	"golang.zabbix.com/sdk/plugin/container"
	"golang.zabbix.com/sdk/zbxerr"
)

const (
	// Name of the plugin.
	Name = "Example"

	myIPMetric  = exampleMetricKey("example.myip")
	goEnvMetric = exampleMetricKey("example.go.env")
)

var (
	_ plugin.Configurator = (*examplePlugin)(nil)
	_ plugin.Exporter     = (*examplePlugin)(nil)
	_ plugin.Runner       = (*examplePlugin)(nil)
)

type exampleMetricKey string

type exampleMetric struct {
	metric  *metric.Metric
	handler handlers.HandlerFunc
}

type examplePlugin struct {
	plugin.Base
	config  *pluginConfig
	metrics map[exampleMetricKey]*exampleMetric
}

// Launch launches the Example plugin. Blocks until plugin execution has
// finished.
func Launch() error {
	p := &examplePlugin{}

	err := p.registerMetrics()
	if err != nil {
		return err
	}

	h, err := container.NewHandler(Name)
	if err != nil {
		return errs.Wrap(err, "failed to create new handler")
	}

	p.Logger = &h

	err = h.Execute()
	if err != nil {
		return errs.Wrap(err, "failed to execute plugin handler")
	}

	return nil
}

// Start starts the example plugin. Is required for plugin to match runner interface.
func (p *examplePlugin) Start() {
	p.Logger.Infof("Start called")
}

// Stop stops the example plugin. Is required for plugin to match runner interface.
func (p *examplePlugin) Stop() {
	p.Logger.Infof("Stop called")
}

// Export collects all the metrics.
func (p *examplePlugin) Export(key string, rawParams []string, _ plugin.ContextProvider) (any, error) {
	m, ok := p.metrics[exampleMetricKey(key)]
	if !ok {
		return nil, errs.Wrapf(zbxerr.ErrorUnsupportedMetric, "unknown metric %q", key)
	}

	metricParams, extraParams, hardcodedParams, err := m.metric.EvalParams(rawParams, p.config.Sessions)
	if err != nil {
		return nil, errs.Wrap(err, "failed to evaluate metric parameters")
	}

	err = metric.SetDefaults(metricParams, hardcodedParams, p.config.Default)
	if err != nil {
		return nil, errs.Wrap(err, "failed to set default params")
	}

	ctx, cancel := context.WithTimeout(
		context.Background(),
		time.Duration(p.config.Timeout)*time.Second,
	)
	defer cancel()

	res, err := m.handler(ctx, metricParams, extraParams...)
	if err != nil {
		return nil, errs.Wrap(err, "failed to execute handler")
	}

	return res, nil
}

func (p *examplePlugin) registerMetrics() error {
	handler := handlers.New()

	p.metrics = map[exampleMetricKey]*exampleMetric{
		myIPMetric: {
			metric: metric.New(
				"Returns the availability groups.",
				params.Params,
				false,
			),
			handler: handlers.WithJSONResponse(
				handlers.WithCredentialValidation(
					handler.MyIP,
				),
			),
		},
		goEnvMetric: {
			metric: metric.New(
				"Returns the result rows of a custom query.",
				params.Params,
				true,
			),
			handler: handlers.WithJSONResponse(
				handlers.WithCredentialValidation(handler.GoEnvironment),
			),
		},
	}

	metricSet := metric.MetricSet{}

	for k, m := range p.metrics {
		metricSet[string(k)] = m.metric
	}

	err := plugin.RegisterMetrics(p, Name, metricSet.List()...)
	if err != nil {
		return errs.Wrap(err, "failed to register metrics")
	}

	return nil
}