// Copyright (C) 2020 Marius Schellenberger

package rcctl

import (
	"github.com/go-kit/kit/log"
	"github.com/go-kit/kit/log/level"
	"github.com/prometheus/client_golang/prometheus"
	"os/exec"
	"strings"
	"sync"
	"time"
)

var (
	specialSvcs = map[string]struct{}{
		"accounting":   struct{}{},
		"check_quotas": struct{}{},
		"ipsec":        struct{}{},
		"library_aslr": struct{}{},
		"multicast":    struct{}{},
		"pf":           struct{}{},
		"spamd_black":  struct{}{},
	}
)

const (
	namespace    = "rcctl"
	rcctl        = "/usr/sbin/rcctl"
	statusOK     = "(ok)"
	statusFailed = "(failed)"

	serviceUpdateInterval = time.Second * 30
)

type Collector struct {
	sync.RWMutex
	state  *prometheus.Desc
	logger log.Logger

	services []string
}

// NewCollector returns a new Collector exposing rcctl statistics.
func NewCollector(logger log.Logger) (*Collector, error) {
	c := &Collector{
		state: prometheus.NewDesc(
			prometheus.BuildFQName(namespace, "", "state"),
			"State of rcctl service.",
			[]string{"service"}, prometheus.Labels{"supervisor": "rcctl"},
		),
		logger: logger,
	}
	go c.getServices()
	return c, nil
}

func (c *Collector) getServices() {
	for {
		var svcs []string
		cmd := exec.Command(rcctl, "ls", "on")
		on, err := cmd.Output()
		if err != nil {
			level.Debug(c.logger).Log("msg", "rcctl list services", "error", err)
			time.Sleep(serviceUpdateInterval)
			continue
		}
		for _, s := range strings.Split(string(on), "\n") {
			if s == "" {
				continue
			}
			if _, ok := specialSvcs[s]; ok {
				continue
			}
			svcs = append(svcs, s)
		}
		c.Lock()
		c.services = svcs
		c.Unlock()
		time.Sleep(serviceUpdateInterval)
	}
}

func (c *Collector) Collect(ch chan<- prometheus.Metric) {
	c.RLock()
	args := append([]string{"check"}, c.services...)
	c.RUnlock()
	cmd := exec.Command(rcctl, args...)
	states, err := cmd.Output()
	if err != nil {
		level.Debug(c.logger).Log("msg", "rcctl check services", "error", err)
	}
	for _, s := range strings.Split(string(states), "\n") {
		if s == "" {
			continue
		}
		state := 0.0
		if strings.Contains(s, statusOK) {
			state = 1.0
			s = strings.TrimSuffix(s, statusOK)
		} else {
			s = strings.TrimSuffix(s, statusFailed)
		}
		level.Debug(c.logger).Log("msg", "Current status", "Service", s, "Status", state)
		ch <- prometheus.MustNewConstMetric(
			c.state, prometheus.GaugeValue, state, s)
	}
	return
}

func (c *Collector) Describe(desc chan<- *prometheus.Desc) {
	desc <- c.state
}
