package transport

import (
	"context"
	"io"
	"net"
	"net/http"
	"strings"
	"sync"
	"testing"
	"time"
)

func TestWritePrometheusFormat(t *testing.T) {
	m := NewMetrics()
	m.IncCounter("txns_applied_total")
	m.AddCounter("txns_applied_total", 41)
	m.SetGauge("peers_total", 5)
	m.SetGauge("nodes_total", 3.5)

	var sb strings.Builder
	if err := m.WritePrometheus(&sb); err != nil {
		t.Fatalf("WritePrometheus: %v", err)
	}
	out := sb.String()

	want := []string{
		"# HELP txns_applied_total Total transactions applied",
		"# TYPE txns_applied_total counter",
		"txns_applied_total 42",
		"# HELP txns_drifted_total Total transactions drifted",
		"# TYPE txns_drifted_total counter",
		"txns_drifted_total 0",
		"# HELP drifts_remediated_total Total drifts remediated",
		"# TYPE drifts_remediated_total counter",
		"drifts_remediated_total 0",
		"# HELP peers_total Current peer count",
		"# TYPE peers_total gauge",
		"peers_total 5",
		"# HELP nodes_total Current node count",
		"# TYPE nodes_total gauge",
		"nodes_total 3.5",
		"# HELP allocs_total Current allocation count",
		"# TYPE allocs_total gauge",
		"allocs_total 0",
	}
	for _, w := range want {
		if !strings.Contains(out, w+"\n") {
			t.Errorf("output missing line %q\n--- got:\n%s", w, out)
		}
	}
}

func TestMetricsConcurrent(t *testing.T) {
	m := NewMetrics()
	var wg sync.WaitGroup
	for i := 0; i < 100; i++ {
		wg.Add(1)
		go func() {
			defer wg.Done()
			m.IncCounter("txns_applied_total")
			m.SetGauge("peers_total", 1)
		}()
	}
	wg.Wait()
	var sb strings.Builder
	if err := m.WritePrometheus(&sb); err != nil {
		t.Fatalf("WritePrometheus: %v", err)
	}
	if !strings.Contains(sb.String(), "txns_applied_total 100\n") {
		t.Errorf("expected 100 applied, got:\n%s", sb.String())
	}
}

func TestMetricsHTTP(t *testing.T) {
	m := NewMetrics()
	m.IncCounter("txns_applied_total")
	m.SetGauge("nodes_total", 7)

	ln, err := net.Listen("tcp", "127.0.0.1:0")
	if err != nil {
		t.Fatalf("listen: %v", err)
	}
	addr := ln.Addr().String()

	mux := http.NewServeMux()
	mux.HandleFunc("/metrics", func(w http.ResponseWriter, r *http.Request) {
		w.Header().Set("Content-Type", "text/plain; version=0.0.4; charset=utf-8")
		_ = m.WritePrometheus(w)
	})
	srv := &http.Server{Handler: mux}

	go srv.Serve(ln)
	defer srv.Shutdown(context.Background())

	time.Sleep(20 * time.Millisecond)

	resp, err := http.Get("http://" + addr + "/metrics")
	if err != nil {
		t.Fatalf("GET /metrics: %v", err)
	}
	defer resp.Body.Close()
	body, err := io.ReadAll(resp.Body)
	if err != nil {
		t.Fatalf("read body: %v", err)
	}
	s := string(body)
	for _, name := range []string{
		"txns_applied_total",
		"txns_drifted_total",
		"drifts_remediated_total",
		"peers_total",
		"nodes_total",
		"allocs_total",
	} {
		if !strings.Contains(s, name) {
			t.Errorf("response missing metric %q", name)
		}
	}
	if !strings.Contains(s, "txns_applied_total 1\n") {
		t.Errorf("counter value wrong in:\n%s", s)
	}
	if !strings.Contains(s, "nodes_total 7\n") {
		t.Errorf("gauge value wrong in:\n%s", s)
	}
}
