package collector

import (
	"log"
	"time"

	"github.com/prometheus/client_golang/prometheus"

	"github.com/boynux/squid-exporter/config"
)

type descMap map[string]*prometheus.Desc

const (
	namespace = "squid"
	timeout   = 10 * time.Second
)

var (
	counters            descMap
	serviceTimes        descMap // ExtractServiceTimes decides if we want to extract service times
	ExtractServiceTimes bool
	infos               descMap
)

// Exporter entry point to Squid exporter.
type Exporter struct {
	client SquidClient

	hostname string
	port     int

	labels config.Labels
	up     *prometheus.GaugeVec
}

type CollectorConfig struct {
	Hostname    string
	Port        int
	Login       string
	Password    string
	Labels      config.Labels
	ProxyHeader string
}

// New initializes a new exporter.
func New(c *CollectorConfig) *Exporter {
	counters = generateSquidCounters(c.Labels.Keys)
	if ExtractServiceTimes {
		serviceTimes = generateSquidServiceTimes(c.Labels.Keys)
	}

	infos = generateSquidInfos(c.Labels.Keys)

	return &Exporter{
		NewCacheObjectClient(c.Hostname, c.Port, c.Login, c.Password, c.ProxyHeader),

		c.Hostname,
		c.Port,

		c.Labels,
		prometheus.NewGaugeVec(prometheus.GaugeOpts{
			Namespace: namespace,
			Name:      "up",
			Help:      "Was the last query of squid successful?",
		}, []string{"host"}),
	}
}

// Describe describes all the metrics ever exported by the ECS exporter. It
// implements prometheus.Collector.
func (e *Exporter) Describe(ch chan<- *prometheus.Desc) {
	e.up.Describe(ch)

	for _, v := range counters {
		ch <- v
	}

	if ExtractServiceTimes {
		for _, v := range serviceTimes {
			ch <- v
		}
	}

	for _, v := range infos {
		ch <- v
	}
}

// Collect fetches metrics from Squid manager and expose them to Prometheus.
func (e *Exporter) Collect(c chan<- prometheus.Metric) {
	insts, err := e.client.GetCounters()

	if err == nil {
		e.up.With(prometheus.Labels{"host": e.hostname}).Set(1)
		for i := range insts {
			if d, ok := counters[insts[i].Key]; ok {
				c <- prometheus.MustNewConstMetric(d, prometheus.CounterValue, insts[i].Value, e.labels.Values...)
			}
		}
	} else {
		e.up.With(prometheus.Labels{"host": e.hostname}).Set(0)
		log.Println("Could not fetch counter metrics from squid instance: ", err)
	}

	if ExtractServiceTimes {
		insts, err = e.client.GetServiceTimes()

		if err == nil {
			for i := range insts {
				if d, ok := serviceTimes[insts[i].Key]; ok {
					c <- prometheus.MustNewConstMetric(d, prometheus.GaugeValue, insts[i].Value, e.labels.Values...)
				}
			}
		} else {
			log.Println("Could not fetch service times metrics from squid instance: ", err)
		}
	}

	insts, err = e.client.GetInfos()
	if err == nil {
		for i := range insts {
			if d, ok := infos[insts[i].Key]; ok {
				c <- prometheus.MustNewConstMetric(d, prometheus.GaugeValue, insts[i].Value, e.labels.Values...)
			} else if insts[i].Key == "squid_info" {
				infoMetricName := prometheus.BuildFQName(namespace, "info", "service")
				var labelsKeys []string
				var labelsValues []string

				for z := range insts[i].VarLabels {
					labelsKeys = append(labelsKeys, insts[i].VarLabels[z].Key)
					labelsValues = append(labelsValues, insts[i].VarLabels[z].Value)
				}

				infoDesc := prometheus.NewDesc(
					infoMetricName,
					"Metrics as string from info on cache_object",
					labelsKeys,
					nil,
				)
				c <- prometheus.MustNewConstMetric(infoDesc, prometheus.GaugeValue, insts[i].Value, labelsValues...)
			}
		}
	} else {
		log.Println("Could not fetch info metrics from squid instance: ", err)
	}

	e.up.Collect(c)
}
