package runtime

import (
	"context"
	"fmt"
	"io"
	"net/http"
	"net/http/httptest"
	"sync/atomic"
	"testing"

	"github.com/prometheus/client_golang/prometheus"
	"github.com/stretchr/testify/require"
)

type fakeExporter struct {
	called *atomic.Bool
}

func (f *fakeExporter) Name() string { return "fake" }

func (f *fakeExporter) Register(_ context.Context, reg prometheus.Registerer) {
	f.called.Store(true)
}

func TestMetricsRequestRegistersExporters(t *testing.T) {
	called := &atomic.Bool{}
	srv := NewServer(Config{
		MetricsPath: "/metrics",
		HealthPath:  "/healthz",
	}, []Exporter{&fakeExporter{called: called}})

	req := httptest.NewRequest(http.MethodGet, "/metrics", http.NoBody)
	rec := httptest.NewRecorder()
	srv.Handler().ServeHTTP(rec, req)

	require.Equal(t, http.StatusOK, rec.Code)
	require.True(t, called.Load())
}

func TestHealthEndpoint(t *testing.T) {
	srv := NewServer(Config{
		MetricsPath: "/metrics",
		HealthPath:  "/healthz",
	}, nil)

	req := httptest.NewRequest(http.MethodGet, "/healthz", http.NoBody)
	rec := httptest.NewRecorder()
	srv.Handler().ServeHTTP(rec, req)

	require.Equal(t, http.StatusOK, rec.Code)
	require.Equal(t, "ok\n", rec.Body.String())
}

type contextMetricExporter struct{}

func (e *contextMetricExporter) Name() string { return "context-metric" }

func (e *contextMetricExporter) Register(ctx context.Context, reg prometheus.Registerer) {
	reg.MustRegister(prometheus.NewGaugeFunc(prometheus.GaugeOpts{
		Name: "test_request_context_done",
		Help: "Whether the scrape request context was canceled.",
	}, func() float64 {
		select {
		case <-ctx.Done():
			return 1
		default:
			return 0
		}
	}))
}

func TestMetricsHandlerPassesRequestContextToExporters(t *testing.T) {
	srv := NewServer(Config{
		MetricsPath: "/metrics",
		HealthPath:  "/healthz",
	}, []Exporter{&contextMetricExporter{}})

	reqCtx, cancel := context.WithCancel(context.Background())
	req := httptest.NewRequest(http.MethodGet, "/metrics", http.NoBody).WithContext(reqCtx)
	cancel()

	rec := httptest.NewRecorder()
	srv.Handler().ServeHTTP(rec, req)

	require.Equal(t, http.StatusOK, rec.Code)
	body, err := io.ReadAll(rec.Body)
	require.NoError(t, err)
	require.Contains(t, string(body), fmt.Sprintf("%s %d", "test_request_context_done", 1))
}
