package runner

// metrics.go — Prometheus metrics HTTP endpoint for the runner.
//
// GET /metrics returns a Prometheus text/plain scrape response.
// Exposes:
//   - Standard Go runtime metrics (via promhttp)
//   - Runner task counters (tasks_running, tasks_completed, tasks_failed)
//   - Asynq queue metrics (registered in asynq_runner.go when Redis is enabled)
//
// GET /health — simple liveness probe.

import (
	"context"
	"fmt"
	"log/slog"
	"net"
	"net/http"
	"time"

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

// metricsServer serves GET /metrics and GET /health for the runner process.
type metricsServer struct {
	addr string
	r    *Runner
	log  *slog.Logger
}

func newMetricsServer(addr string, r *Runner, log *slog.Logger) *metricsServer {
	ms := &metricsServer{addr: addr, r: r, log: log}
	ms.registerCollectors()
	return ms
}

// registerCollectors registers runner-specific Prometheus collectors.
// Called once at construction; Asynq queue collector is registered separately
// in asynq_runner.go after the Redis connection is established.
func (ms *metricsServer) registerCollectors() {
	// Runner task gauges / counters backed by the atomic fields on Runner.
	ms.r.metricsRegistry.MustRegister(prometheus.NewGaugeFunc(
		prometheus.GaugeOpts{
			Namespace: "runner",
			Name:      "tasks_running",
			Help:      "Number of tasks currently being processed.",
		},
		func() float64 { return float64(ms.r.tasksRunning.Load()) },
	))
	ms.r.metricsRegistry.MustRegister(prometheus.NewCounterFunc(
		prometheus.CounterOpts{
			Namespace: "runner",
			Name:      "tasks_completed_total",
			Help:      "Total number of tasks completed successfully since startup.",
		},
		func() float64 { return float64(ms.r.tasksCompleted.Load()) },
	))
	ms.r.metricsRegistry.MustRegister(prometheus.NewCounterFunc(
		prometheus.CounterOpts{
			Namespace: "runner",
			Name:      "tasks_failed_total",
			Help:      "Total number of tasks that ended in failure since startup.",
		},
		func() float64 { return float64(ms.r.tasksFailed.Load()) },
	))
	ms.r.metricsRegistry.MustRegister(prometheus.NewGaugeFunc(
		prometheus.GaugeOpts{
			Namespace: "runner",
			Name:      "uptime_seconds",
			Help:      "Seconds since the runner process started.",
		},
		func() float64 { return time.Since(ms.r.startedAt).Seconds() },
	))
}

// ListenAndServe starts the HTTP server and blocks until ctx is cancelled or
// a fatal listen error occurs.
func (ms *metricsServer) ListenAndServe(ctx context.Context) error {
	mux := http.NewServeMux()
	mux.Handle("GET /metrics", promhttp.HandlerFor(ms.r.metricsRegistry, promhttp.HandlerOpts{}))
	mux.HandleFunc("GET /health", ms.handleHealth)

	srv := &http.Server{
		Addr:         ms.addr,
		Handler:      mux,
		ReadTimeout:  5 * time.Second,
		WriteTimeout: 10 * time.Second,
		BaseContext:  func(_ net.Listener) context.Context { return ctx },
	}

	errCh := make(chan error, 1)
	go func() {
		ms.log.Info("runner: metrics server listening", "addr", ms.addr)
		errCh <- srv.ListenAndServe()
	}()

	select {
	case <-ctx.Done():
		shutCtx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
		defer cancel()
		_ = srv.Shutdown(shutCtx)
		return nil
	case err := <-errCh:
		return fmt.Errorf("runner: metrics server: %w", err)
	}
}

// handleHealth handles GET /health — simple liveness probe.
func (ms *metricsServer) handleHealth(w http.ResponseWriter, _ *http.Request) {
	w.Header().Set("Content-Type", "application/json")
	_, _ = w.Write([]byte(`{"status":"ok"}`))
}
