package runtimeprocess

import (
	"errors"
	"net/http"
	"time"

	"github.com/prometheus/client_golang/prometheus"
	"github.com/prometheus/client_golang/prometheus/collectors"
	"github.com/prometheus/client_golang/prometheus/promhttp"
)

const MetricsPath = "/metrics"

type processCollector struct {
	process        *Process
	info           *prometheus.Desc
	live           *prometheus.Desc
	ready          *prometheus.Desc
	readyKnown     *prometheus.Desc
	readyCheckedAt *prometheus.Desc
	state          *prometheus.Desc
	limit          *prometheus.Desc
	inFlight       *prometheus.Desc
	accepted       *prometheus.Desc
	rejected       *prometheus.Desc
}

func newMetricsHandler(process *Process) (http.Handler, error) {
	if process == nil {
		return nil, errors.New("runtime process metrics: process is required")
	}
	registry := prometheus.NewRegistry()
	collector := &processCollector{
		process:        process,
		info:           prometheus.NewDesc("nexuscrm_runtime_process_info", "Immutable Runtime process identity.", []string{"role", "version"}, nil),
		live:           prometheus.NewDesc("nexuscrm_runtime_process_live", "Whether the Runtime process is live.", []string{"role"}, nil),
		ready:          prometheus.NewDesc("nexuscrm_runtime_process_ready", "Latest actively probed readiness result, additionally fenced by current process lifecycle state.", []string{"role"}, nil),
		readyKnown:     prometheus.NewDesc("nexuscrm_runtime_process_readiness_check_known", "Whether an active readiness result is available for the current process.", []string{"role"}, nil),
		readyCheckedAt: prometheus.NewDesc("nexuscrm_runtime_process_readiness_last_check_timestamp_seconds", "Unix timestamp of the latest completed active readiness check; zero until first check.", []string{"role"}, nil),
		state:          prometheus.NewDesc("nexuscrm_runtime_process_state", "Current Runtime lifecycle state (assembled=0, started=1, ready=2, draining=3, stopped=4).", []string{"role"}, nil),
		limit:          prometheus.NewDesc("nexuscrm_runtime_admission_limit", "Configured per-process fail-fast admission bound.", []string{"role"}, nil),
		inFlight:       prometheus.NewDesc("nexuscrm_runtime_admission_in_flight", "Currently admitted Runtime requests.", []string{"role"}, nil),
		accepted:       prometheus.NewDesc("nexuscrm_runtime_admission_accepted_total", "Runtime requests accepted by the process-local admission gate.", []string{"role"}, nil),
		rejected:       prometheus.NewDesc("nexuscrm_runtime_admission_rejected_total", "Runtime requests rejected by the process-local admission gate.", []string{"role", "reason"}, nil),
	}
	if err := registry.Register(collector); err != nil {
		return nil, err
	}
	if process.config.MetricsCollector != nil {
		if err := registry.Register(process.config.MetricsCollector); err != nil {
			return nil, err
		}
	}
	for _, collector := range process.config.AdditionalMetricsCollectors {
		if collector == nil {
			continue
		}
		if err := registry.Register(collector); err != nil {
			return nil, err
		}
	}
	if err := registry.Register(collectors.NewGoCollector()); err != nil {
		return nil, err
	}
	if err := registry.Register(collectors.NewProcessCollector(collectors.ProcessCollectorOpts{})); err != nil {
		return nil, err
	}
	return promhttp.HandlerFor(registry, promhttp.HandlerOpts{}), nil
}

func (c *processCollector) Describe(ch chan<- *prometheus.Desc) {
	ch <- c.info
	ch <- c.live
	ch <- c.ready
	ch <- c.readyKnown
	ch <- c.readyCheckedAt
	ch <- c.state
	ch <- c.limit
	ch <- c.inFlight
	ch <- c.accepted
	ch <- c.rejected
}

func (c *processCollector) Collect(ch chan<- prometheus.Metric) {
	role := c.process.config.Role
	ch <- prometheus.MustNewConstMetric(c.info, prometheus.GaugeValue, 1, role, c.process.config.Version)
	ch <- prometheus.MustNewConstMetric(c.live, prometheus.GaugeValue, boolFloat(c.process.Live()), role)
	readiness := c.process.readinessSnapshot()
	ch <- prometheus.MustNewConstMetric(c.ready, prometheus.GaugeValue, boolFloat(readiness.Ready), role)
	ch <- prometheus.MustNewConstMetric(c.readyKnown, prometheus.GaugeValue, boolFloat(readiness.Known), role)
	ch <- prometheus.MustNewConstMetric(c.readyCheckedAt, prometheus.GaugeValue, unixTimestamp(readiness.CheckedAt), role)
	ch <- prometheus.MustNewConstMetric(c.state, prometheus.GaugeValue, float64(c.process.State()), role)
	if c.process.config.Admission != nil {
		snapshot := c.process.config.Admission()
		ch <- prometheus.MustNewConstMetric(c.limit, prometheus.GaugeValue, float64(snapshot.Limit), role)
		ch <- prometheus.MustNewConstMetric(c.inFlight, prometheus.GaugeValue, float64(snapshot.InFlight), role)
		ch <- prometheus.MustNewConstMetric(c.accepted, prometheus.CounterValue, float64(snapshot.Accepted), role)
		ch <- prometheus.MustNewConstMetric(c.rejected, prometheus.CounterValue, float64(snapshot.RejectedFull), role, "capacity")
		ch <- prometheus.MustNewConstMetric(c.rejected, prometheus.CounterValue, float64(snapshot.RejectedDraining), role, "draining")
	}
}

func unixTimestamp(value time.Time) float64 {
	if value.IsZero() {
		return 0
	}
	return float64(value.UnixNano()) / float64(time.Second)
}

func boolFloat(value bool) float64 {
	if value {
		return 1
	}
	return 0
}
