package probers

import (
	"fmt"
	"net/url"

	"github.com/prometheus/client_golang/prometheus"

	"github.com/letsencrypt/boulder/observer/probers"
	"github.com/letsencrypt/boulder/strictyaml"
)

const (
	nextUpdateName = "obs_crl_next_update"
	thisUpdateName = "obs_crl_this_update"
	certCountName  = "obs_crl_revoked_cert_count"
)

// CRLConf is exported to receive YAML configuration
type CRLConf struct {
	URL         string `yaml:"url"`
	Partitioned bool   `yaml:"partitioned"`
}

// Kind returns a name that uniquely identifies the `Kind` of `Configurer`.
func (c CRLConf) Kind() string {
	return "CRL"
}

// UnmarshalSettings constructs a CRLConf object from YAML as bytes.
func (c CRLConf) UnmarshalSettings(settings []byte) (probers.Configurer, error) {
	var conf CRLConf
	err := strictyaml.Unmarshal(settings, &conf)

	if err != nil {
		return nil, err
	}
	return conf, nil
}

func (c CRLConf) validateURL() error {
	url, err := url.Parse(c.URL)
	if err != nil {
		return fmt.Errorf("invalid 'url', got: %q, expected a valid url", c.URL)
	}
	if url.Scheme == "" {
		return fmt.Errorf("invalid 'url', got: %q, missing scheme", c.URL)
	}
	if url.Scheme != "http" {
		return fmt.Errorf("invalid 'url', got: %q, scheme must be http", c.URL)
	}
	return nil
}

// MakeProber constructs a `CRLProbe` object from the contents of the
// bound `CRLConf` object. If the `CRLConf` cannot be validated, an
// error appropriate for end-user consumption is returned instead.
func (c CRLConf) MakeProber(collectors map[string]prometheus.Collector) (probers.Prober, error) { // validate `url` err := c.validateURL()
	// validate `url`
	err := c.validateURL()
	if err != nil {
		return nil, err
	}

	// validate the prometheus collectors that were passed in
	coll, ok := collectors[nextUpdateName]
	if !ok {
		return nil, fmt.Errorf("crl prober did not receive collector %q", nextUpdateName)
	}
	nextUpdateColl, ok := coll.(*prometheus.GaugeVec)
	if !ok {
		return nil, fmt.Errorf("crl prober received collector %q of wrong type, got: %T, expected *prometheus.GaugeVec", nextUpdateName, coll)
	}

	coll, ok = collectors[thisUpdateName]
	if !ok {
		return nil, fmt.Errorf("crl prober did not receive collector %q", thisUpdateName)
	}
	thisUpdateColl, ok := coll.(*prometheus.GaugeVec)
	if !ok {
		return nil, fmt.Errorf("crl prober received collector %q of wrong type, got: %T, expected *prometheus.GaugeVec", thisUpdateName, coll)
	}

	coll, ok = collectors[certCountName]
	if !ok {
		return nil, fmt.Errorf("crl prober did not receive collector %q", certCountName)
	}
	certCountColl, ok := coll.(*prometheus.GaugeVec)
	if !ok {
		return nil, fmt.Errorf("crl prober received collector %q of wrong type, got: %T, expected *prometheus.GaugeVec", certCountName, coll)
	}

	return CRLProbe{c.URL, c.Partitioned, nextUpdateColl, thisUpdateColl, certCountColl}, nil
}

// Instrument constructs any `prometheus.Collector` objects the `CRLProbe` will
// need to report its own metrics. A map is returned containing the constructed
// objects, indexed by the name of the prometheus metric. If no objects were
// constructed, nil is returned.
func (c CRLConf) Instrument() map[string]prometheus.Collector {
	nextUpdate := prometheus.Collector(prometheus.NewGaugeVec(
		prometheus.GaugeOpts{
			Name: nextUpdateName,
			Help: "CRL nextUpdate Unix timestamp in seconds",
		}, []string{"url"},
	))
	thisUpdate := prometheus.Collector(prometheus.NewGaugeVec(
		prometheus.GaugeOpts{
			Name: thisUpdateName,
			Help: "CRL thisUpdate Unix timestamp in seconds",
		}, []string{"url"},
	))
	certCount := prometheus.Collector(prometheus.NewGaugeVec(
		prometheus.GaugeOpts{
			Name: certCountName,
			Help: "number of certificates revoked in CRL",
		}, []string{"url"},
	))
	return map[string]prometheus.Collector{
		nextUpdateName: nextUpdate,
		thisUpdateName: thisUpdate,
		certCountName:  certCount,
	}
}

// init is called at runtime and registers `CRLConf`, a `Prober`
// `Configurer` type, as "CRL".
func init() {
	probers.Register(CRLConf{})
}
