package main

import (
	"github.com/prometheus/client_golang/prometheus"
)

// DriveExporter is a Prometheus collector that, on each scrape, discovers the
// host's drives and emits their numeric health columns as gauges. The label set
// (the schema's string columns) is attached to every metric so the Prometheus
// and InfluxDB outputs describe each drive identically.
type DriveExporter struct {
	descs  map[string]*prometheus.Desc
	labels []column // String columns carried as labels.
	gauges []column // Numeric columns emitted as gauges.
	// collect discovers the drives; a field so tests can inject a fixed set
	// without touching real hardware.
	collect func() ([]*Drive, int64)
}

// NewDriveExporter builds the collector with one gauge descriptor per numeric
// column, labelled with the schema's string columns.
func NewDriveExporter() *DriveExporter {
	labels, gauges := labelColumns(), gaugeColumns()
	labelNames := make([]string, len(labels))
	for i, c := range labels {
		labelNames[i] = c.name
	}
	descs := make(map[string]*prometheus.Desc, len(gauges))
	for _, c := range gauges {
		descs[c.name] = prometheus.NewDesc(namespace+"_"+c.name, "drive health metric: "+c.name, labelNames, nil)
	}
	return &DriveExporter{descs: descs, labels: labels, gauges: gauges, collect: collect}
}

// Reload is a no-op; the exporter holds no configurable state.
func (e *DriveExporter) Reload() {}

// Describe sends every metric descriptor to the channel.
func (e *DriveExporter) Describe(ch chan<- *prometheus.Desc) {
	for _, d := range e.descs {
		ch <- d
	}
}

// Collect discovers the drives and emits a gauge per numeric column, with the
// shared identity label set.
func (e *DriveExporter) Collect(ch chan<- prometheus.Metric) {
	drives, _ := e.collect()
	for _, d := range drives {
		labelValues := make([]string, len(e.labels))
		for i, c := range e.labels {
			labelValues[i] = format(c.raw(d))
		}
		for _, c := range e.gauges {
			val, ok := gaugeValue(c, d)
			if !ok {
				continue
			}
			ch <- prometheus.MustNewConstMetric(e.descs[c.name], prometheus.GaugeValue, val, labelValues...)
		}
	}
}

// gaugeValue converts a numeric column's value to a float for Prometheus,
// reporting ok=false when the value is unknown.
func gaugeValue(c column, d *Drive) (float64, bool) {
	r := c.raw(d)
	switch t := r.(type) {
	case nil:
		return 0, false
	case int:
		return float64(t), true
	case float64:
		return t, true
	case bool:
		if t {
			return 1, true
		}
		return 0, true
	default:
		return 0, false
	}
}
