package puppetagent

import (
	"fmt"
	"os"
	"reflect"
	"strings"

	"gopkg.in/yaml.v2"

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

// PuppetAgent is a PuppetAgent plugin
type PuppetAgent struct {
	Location string
}

var sampleConfig = `
  ## Location of puppet last run summary file
  location = "/var/lib/puppet/state/last_run_summary.yaml"
`

type State struct {
	Events    event
	Resources resource
	Changes   change
	Time      time
	Version   version
}

type event struct {
	Failure int64 `yaml:"failure"`
	Noop    int64 `yaml:"noop"`
	Total   int64 `yaml:"total"`
	Success int64 `yaml:"success"`
}

type resource struct {
	Changed          int64 `yaml:"changed"`
	CorrectiveChange int64 `yaml:"corrective_change"`
	Failed           int64 `yaml:"failed"`
	FailedToRestart  int64 `yaml:"failed_to_restart"`
	OutOfSync        int64 `yaml:"out_of_sync"`
	Restarted        int64 `yaml:"restarted"`
	Scheduled        int64 `yaml:"scheduled"`
	Skipped          int64 `yaml:"skipped"`
	Total            int64 `yaml:"total"`
}

type change struct {
	Total int64 `yaml:"total"`
}

type time struct {
	Anchor                float64 `yaml:"anchor"`
	CataLogApplication    float64 `yaml:"catalog_application"`
	ConfigRetrieval       float64 `yaml:"config_retrieval"`
	ConvertCatalog        float64 `yaml:"convert_catalog"`
	Cron                  float64 `yaml:"cron"`
	Exec                  float64 `yaml:"exec"`
	FactGeneration        float64 `yaml:"fact_generation"`
	File                  float64 `yaml:"file"`
	FileBucket            float64 `yaml:"filebucket"`
	Group                 float64 `yaml:"group"`
	LastRun               int64   `yaml:"last_run"`
	NodeRetrieval         float64 `yaml:"node_retrieval"`
	Notify                float64 `yaml:"notify"`
	Package               float64 `yaml:"package"`
	PluginSync            float64 `yaml:"plugin_sync"`
	Schedule              float64 `yaml:"schedule"`
	Service               float64 `yaml:"service"`
	SSHAuthorizedKey      float64 `yaml:"ssh_authorized_key"`
	Total                 float64 `yaml:"total"`
	TransactionEvaluation float64 `yaml:"transaction_evaluation"`
	User                  float64 `yaml:"user"`
}

type version struct {
	ConfigString string `yaml:"config"`
	Puppet       string `yaml:"puppet"`
}

// SampleConfig returns sample configuration message
func (pa *PuppetAgent) SampleConfig() string {
	return sampleConfig
}

// Description returns description of PuppetAgent plugin
func (pa *PuppetAgent) Description() string {
	return `Reads last_run_summary.yaml file and converts to measurements`
}

// Gather reads stats from all configured servers accumulates stats
func (pa *PuppetAgent) Gather(acc telegraf.Accumulator) error {
	if len(pa.Location) == 0 {
		pa.Location = "/var/lib/puppet/state/last_run_summary.yaml"
	}

	if _, err := os.Stat(pa.Location); err != nil {
		return fmt.Errorf("%s", err)
	}

	fh, err := os.ReadFile(pa.Location)
	if err != nil {
		return fmt.Errorf("%s", err)
	}

	var puppetState State

	err = yaml.Unmarshal(fh, &puppetState)
	if err != nil {
		return fmt.Errorf("%s", err)
	}

	tags := map[string]string{"location": pa.Location}
	structPrinter(&puppetState, acc, tags)

	return nil
}

func structPrinter(s *State, acc telegraf.Accumulator, tags map[string]string) {
	e := reflect.ValueOf(s).Elem()

	fields := make(map[string]interface{})
	for tLevelFNum := 0; tLevelFNum < e.NumField(); tLevelFNum++ {
		name := e.Type().Field(tLevelFNum).Name
		nameNumField := e.FieldByName(name).NumField()

		for sLevelFNum := 0; sLevelFNum < nameNumField; sLevelFNum++ {
			sName := e.FieldByName(name).Type().Field(sLevelFNum).Name
			sValue := e.FieldByName(name).Field(sLevelFNum).Interface()

			lname := strings.ToLower(name)
			lsName := strings.ToLower(sName)
			fields[fmt.Sprintf("%s_%s", lname, lsName)] = sValue
		}
	}
	acc.AddFields("puppetagent", fields, tags)
}

func init() {
	inputs.Add("puppetagent", func() telegraf.Input {
		return &PuppetAgent{}
	})
}
