package collectors

import (
	"encoding/json"
	"fmt"
	"io/ioutil"
	"path/filepath"
	"strconv"
	"strings"
	"time"

	cclog "github.com/ClusterCockpit/cc-metric-collector/internal/ccLogger"
	lp "github.com/ClusterCockpit/cc-metric-collector/internal/ccMetric"
)

// See: https://www.kernel.org/doc/html/latest/hwmon/sysfs-interface.html
// /sys/class/hwmon/hwmon*/name -> coretemp
// /sys/class/hwmon/hwmon*/temp*_label -> Core 0
// /sys/class/hwmon/hwmon*/temp*_input -> 27800 = 27.8°C
// /sys/class/hwmon/hwmon*/temp*_max -> 86000 = 86.0°C
// /sys/class/hwmon/hwmon*/temp*_crit -> 100000 = 100.0°C

type TempCollectorSensor struct {
	name       string
	label      string
	metricName string // Default: name_label
	file       string
	tags       map[string]string
}

type TempCollector struct {
	metricCollector
	config struct {
		ExcludeMetrics []string                     `json:"exclude_metrics"`
		TagOverride    map[string]map[string]string `json:"tag_override"`
	}
	sensors []*TempCollectorSensor
}

func (m *TempCollector) Init(config json.RawMessage) error {
	// Check if already initialized
	if m.init {
		return nil
	}

	m.name = "TempCollector"
	m.setup()
	if len(config) > 0 {
		err := json.Unmarshal(config, &m.config)
		if err != nil {
			return err
		}
	}

	m.meta = map[string]string{
		"source": m.name,
		"group":  "IPMI",
		"unit":   "degC",
	}

	m.sensors = make([]*TempCollectorSensor, 0)

	// Find all temperature sensor files
	globPattern := filepath.Join("/sys/class/hwmon", "*", "temp*_input")
	inputFiles, err := filepath.Glob(globPattern)
	if err != nil {
		return fmt.Errorf("Unable to glob files with pattern '%s': %v", globPattern, err)
	}
	if inputFiles == nil {
		return fmt.Errorf("Unable to find any files with pattern '%s'", globPattern)
	}

	// Get sensor name for each temperature sensor file
	for _, file := range inputFiles {
		sensor := new(TempCollectorSensor)

		// sensor name
		nameFile := filepath.Join(filepath.Dir(file), "name")
		name, err := ioutil.ReadFile(nameFile)
		if err == nil {
			sensor.name = strings.TrimSpace(string(name))
		}

		// sensor label
		labelFile := strings.TrimSuffix(file, "_input") + "_label"
		label, err := ioutil.ReadFile(labelFile)
		if err == nil {
			sensor.label = strings.TrimSpace(string(label))
		}

		// sensor metric name
		switch {
		case len(sensor.name) == 0 && len(sensor.label) == 0:
			continue
		case len(sensor.name) != 0 && len(sensor.label) != 0:
			sensor.metricName = sensor.name + "_" + sensor.label
		case len(sensor.name) != 0:
			sensor.metricName = sensor.name
		case len(sensor.label) != 0:
			sensor.metricName = sensor.label
		}
		sensor.metricName = strings.Replace(sensor.metricName, " ", "_", -1)
		// Add temperature prefix, if required
		if !strings.Contains(sensor.metricName, "temp") {
			sensor.metricName = "temp_" + sensor.metricName
		}
		sensor.metricName = strings.ToLower(sensor.metricName)

		// Sensor file
		sensor.file = file

		// Sensor tags
		sensor.tags = map[string]string{
			"type": "node",
		}

		// Apply tag override configuration
		for key, newtags := range m.config.TagOverride {
			if strings.Contains(sensor.file, key) {
				sensor.tags = newtags
				break
			}
		}

		m.sensors = append(m.sensors, sensor)
	}

	// Empty sensors map
	if len(m.sensors) == 0 {
		return fmt.Errorf("No temperature sensors found")
	}

	// Finished initialization
	m.init = true
	return nil
}

func (m *TempCollector) Read(interval time.Duration, output chan lp.CCMetric) {

	for _, sensor := range m.sensors {
		// Read sensor file
		buffer, err := ioutil.ReadFile(sensor.file)
		if err != nil {
			cclog.ComponentError(
				m.name,
				fmt.Sprintf("Read(): Failed to read file '%s': %v", sensor.file, err))
			continue
		}
		x, err := strconv.ParseInt(strings.TrimSpace(string(buffer)), 10, 64)
		if err != nil {
			cclog.ComponentError(
				m.name,
				fmt.Sprintf("Read(): Failed to convert temperature '%s' to int64: %v", buffer, err))
			continue
		}
		x /= 1000
		y, err := lp.New(
			sensor.metricName,
			sensor.tags,
			m.meta,
			map[string]interface{}{"value": x},
			time.Now(),
		)
		if err == nil {
			output <- y
		}
	}

}

func (m *TempCollector) Close() {
	m.init = false
}