//go:generate ../../../tools/readme_config_includer/generator
package fluentd

import (
	_ "embed"
	"encoding/json"
	"errors"
	"fmt"
	"io"
	"net/http"
	"net/url"
	"time"

	"github.com/influxdata/telegraf"
	"github.com/influxdata/telegraf/plugins/inputs"
)

//go:embed sample.conf
var sampleConfig string

const measurement = "fluentd"

type Fluentd struct {
	Endpoint string   `toml:"endpoint"`
	Exclude  []string `toml:"exclude"`

	client *http.Client
}

type endpointInfo struct {
	Payload []pluginData `json:"plugins"`
}

type pluginData struct {
	PluginID               string   `json:"plugin_id"`
	PluginType             string   `json:"type"`
	PluginCategory         string   `json:"plugin_category"`
	RetryCount             *float64 `json:"retry_count"`
	BufferQueueLength      *float64 `json:"buffer_queue_length"`
	BufferTotalQueuedSize  *float64 `json:"buffer_total_queued_size"`
	RollbackCount          *float64 `json:"rollback_count"`
	EmitRecords            *float64 `json:"emit_records"`
	EmitSize               *float64 `json:"emit_size"`
	EmitCount              *float64 `json:"emit_count"`
	WriteCount             *float64 `json:"write_count"`
	SlowFlushCount         *float64 `json:"slow_flush_count"`
	FlushTimeCount         *float64 `json:"flush_time_count"`
	BufferStageLength      *float64 `json:"buffer_stage_length"`
	BufferStageByteSize    *float64 `json:"buffer_stage_byte_size"`
	BufferQueueByteSize    *float64 `json:"buffer_queue_byte_size"`
	AvailBufferSpaceRatios *float64 `json:"buffer_available_buffer_space_ratios"`
}

func (*Fluentd) SampleConfig() string {
	return sampleConfig
}

func (h *Fluentd) Gather(acc telegraf.Accumulator) error {
	_, err := url.Parse(h.Endpoint)
	if err != nil {
		return fmt.Errorf("invalid URL %q", h.Endpoint)
	}

	if h.client == nil {
		tr := &http.Transport{
			ResponseHeaderTimeout: 3 * time.Second,
		}

		client := &http.Client{
			Transport: tr,
			Timeout:   4 * time.Second,
		}

		h.client = client
	}

	resp, err := h.client.Get(h.Endpoint)

	if err != nil {
		return fmt.Errorf("unable to perform HTTP client GET on %q: %w", h.Endpoint, err)
	}

	defer resp.Body.Close()

	body, err := io.ReadAll(resp.Body)

	if err != nil {
		return fmt.Errorf("unable to read the HTTP body %q: %w", string(body), err)
	}

	if resp.StatusCode != http.StatusOK {
		return errors.New("http status ok not met")
	}

	dataPoints, err := parse(body)

	if err != nil {
		return errors.New("problem with parsing")
	}

	// Go through all plugins one by one
	for _, p := range dataPoints {
		skip := false

		// Check if this specific type was excluded in configuration
		for _, exclude := range h.Exclude {
			if exclude == p.PluginType {
				skip = true
			}
		}

		// If not, create new metric and add it to Accumulator
		if !skip {
			tmpFields := make(map[string]interface{})

			tmpTags := map[string]string{
				"plugin_id":       p.PluginID,
				"plugin_category": p.PluginCategory,
				"plugin_type":     p.PluginType,
			}

			if p.BufferQueueLength != nil {
				tmpFields["buffer_queue_length"] = *p.BufferQueueLength
			}

			if p.RetryCount != nil {
				tmpFields["retry_count"] = *p.RetryCount
			}

			if p.BufferTotalQueuedSize != nil {
				tmpFields["buffer_total_queued_size"] = *p.BufferTotalQueuedSize
			}

			if p.RollbackCount != nil {
				tmpFields["rollback_count"] = *p.RollbackCount
			}

			if p.EmitRecords != nil {
				tmpFields["emit_records"] = *p.EmitRecords
			}

			if p.EmitCount != nil {
				tmpFields["emit_count"] = *p.EmitCount
			}

			if p.EmitSize != nil {
				tmpFields["emit_size"] = *p.EmitSize
			}

			if p.WriteCount != nil {
				tmpFields["write_count"] = *p.WriteCount
			}

			if p.SlowFlushCount != nil {
				tmpFields["slow_flush_count"] = *p.SlowFlushCount
			}

			if p.FlushTimeCount != nil {
				tmpFields["flush_time_count"] = *p.FlushTimeCount
			}

			if p.BufferStageLength != nil {
				tmpFields["buffer_stage_length"] = *p.BufferStageLength
			}

			if p.BufferStageByteSize != nil {
				tmpFields["buffer_stage_byte_size"] = *p.BufferStageByteSize
			}

			if p.BufferQueueByteSize != nil {
				tmpFields["buffer_queue_byte_size"] = *p.BufferQueueByteSize
			}

			if p.AvailBufferSpaceRatios != nil {
				tmpFields["buffer_available_buffer_space_ratios"] = *p.AvailBufferSpaceRatios
			}

			if !((p.BufferQueueLength == nil) &&
				(p.RetryCount == nil) &&
				(p.BufferTotalQueuedSize == nil) &&
				(p.EmitCount == nil) &&
				(p.EmitRecords == nil) &&
				(p.EmitSize == nil) &&
				(p.WriteCount == nil) &&
				(p.FlushTimeCount == nil) &&
				(p.SlowFlushCount == nil) &&
				(p.RollbackCount == nil) &&
				(p.BufferStageLength == nil) &&
				(p.BufferStageByteSize == nil) &&
				(p.BufferQueueByteSize == nil) &&
				(p.AvailBufferSpaceRatios == nil)) {
				acc.AddFields(measurement, tmpFields, tmpTags)
			}
		}
	}

	return nil
}

// parse JSON from fluentd Endpoint
// Parameters:
//
//	data: unprocessed json received from endpoint
//
// Returns:
//
//	pluginData:		slice that contains parsed plugins
//	error:			error that may have occurred
func parse(data []byte) (datapointArray []pluginData, err error) {
	var endpointData endpointInfo

	if err = json.Unmarshal(data, &endpointData); err != nil {
		err = errors.New("processing JSON structure")
		return nil, err
	}

	datapointArray = append(datapointArray, endpointData.Payload...)
	return datapointArray, err
}

func init() {
	inputs.Add("fluentd", func() telegraf.Input { return &Fluentd{} })
}
