package infra

import (
	"os"
	"testing"
)

// findNatsSample devuelve el primer PromSample cuyo Name coincide y cuyos labels
// extra (clave/valor alternados) están todos presentes con el valor esperado.
// El segundo retorno indica si se encontró.
func findNatsSample(samples []PromSample, name string, labels ...string) (PromSample, bool) {
	for _, s := range samples {
		if s.Name != name {
			continue
		}
		match := true
		for i := 0; i+1 < len(labels); i += 2 {
			if s.Labels[labels[i]] != labels[i+1] {
				match = false
				break
			}
		}
		if match {
			return s, true
		}
	}
	return PromSample{}, false
}

func mustRead(t *testing.T, path string) []byte {
	t.Helper()
	b, err := os.ReadFile(path)
	if err != nil {
		t.Fatalf("read fixture %s: %v", path, err)
	}
	return b
}

// golden: fixtures reales de un nats-server 2.11.15, node="probe" (== el leader
// de los streams), valores concretos verificados a mano.
func TestParseNatsMonitorGolden(t *testing.T) {
	varz := mustRead(t, "testdata/nats_varz.json")
	connz := mustRead(t, "testdata/nats_connz.json")
	jsz := mustRead(t, "testdata/nats_jsz.json")

	got, err := ParseNatsMonitor("probe", varz, connz, jsz)
	if err != nil {
		t.Fatalf("unexpected error: %v", err)
	}

	want := map[string]float64{
		"nats_msgs_in_total":      17,
		"nats_msgs_out_total":     17,
		"nats_mem_bytes":          18288640,
		"nats_jetstream_streams":  3,
		"nats_connections":        1,
		"nats_jetstream_messages": 6,
	}
	for name, w := range want {
		s, ok := findNatsSample(got, name)
		if !ok {
			t.Errorf("missing sample %q", name)
			continue
		}
		if s.Value != w {
			t.Errorf("%s = %v, want %v", name, s.Value, w)
		}
		if s.Labels["node"] != "probe" || s.Labels["instance"] != "probe" {
			t.Errorf("%s labels = %v, want node=instance=probe", name, s.Labels)
		}
	}

	// kv_bucket_msgs por cada KV bucket (prefijo KV_ recortado).
	for bucket, w := range map[string]float64{
		"UNIBUS_users":   2,
		"UNIBUS_rooms":   2,
		"UNIBUS_members": 2,
	} {
		s, ok := findNatsSample(got, "kv_bucket_msgs", "bucket", bucket)
		if !ok {
			t.Errorf("missing kv_bucket_msgs{bucket=%q}", bucket)
			continue
		}
		if s.Value != w {
			t.Errorf("kv_bucket_msgs{bucket=%q} = %v, want %v", bucket, s.Value, w)
		}
	}

	// raft leader: probe == node, así que el stream KV_UNIBUS_users tiene leader=1.
	s, ok := findNatsSample(got, "nats_jetstream_raft_leader", "stream", "KV_UNIBUS_users")
	if !ok {
		t.Fatal("missing nats_jetstream_raft_leader{stream=KV_UNIBUS_users}")
	}
	if s.Value != 1 {
		t.Errorf("nats_jetstream_raft_leader{stream=KV_UNIBUS_users} = %v, want 1", s.Value)
	}

	// stream_detail también emite nats_stream_messages con label stream completo.
	if s, ok := findNatsSample(got, "nats_stream_messages", "stream", "KV_UNIBUS_users"); !ok || s.Value != 2 {
		t.Errorf("nats_stream_messages{stream=KV_UNIBUS_users} = %v ok=%v, want 2", s.Value, ok)
	}

	// nats_server_start_seconds presente (start es RFC3339 válido).
	if _, ok := findNatsSample(got, "nats_server_start_seconds"); !ok {
		t.Error("missing nats_server_start_seconds (start is a valid RFC3339)")
	}
}

// edge: jsz sin streams ni account_details. No produce series kv_bucket_msgs ni
// nats_stream_*, pero sí las de varz/connz y las jetstream top-level (en 0).
func TestParseNatsMonitorEmptyJsz(t *testing.T) {
	varz := mustRead(t, "testdata/nats_varz.json")
	connz := mustRead(t, "testdata/nats_connz.json")
	jsz := []byte(`{"streams":0,"account_details":[]}`)

	got, err := ParseNatsMonitor("probe", varz, connz, jsz)
	if err != nil {
		t.Fatalf("unexpected error: %v", err)
	}
	if _, ok := findNatsSample(got, "kv_bucket_msgs", "bucket", "UNIBUS_users"); ok {
		t.Error("did not expect kv_bucket_msgs with empty account_details")
	}
	if _, ok := findNatsSample(got, "nats_stream_messages"); ok {
		t.Error("did not expect nats_stream_messages with empty account_details")
	}
	// varz/connz siguen presentes.
	if s, ok := findNatsSample(got, "nats_msgs_in_total"); !ok || s.Value != 17 {
		t.Errorf("nats_msgs_in_total = %v ok=%v, want 17", s.Value, ok)
	}
	if s, ok := findNatsSample(got, "nats_connections"); !ok || s.Value != 1 {
		t.Errorf("nats_connections = %v ok=%v, want 1", s.Value, ok)
	}
}

// edge: connz inválido. No es error; nats_connections cae a varz.connections (1).
// varz/jsz siguen produciendo sus series.
func TestParseNatsMonitorInvalidConnz(t *testing.T) {
	varz := mustRead(t, "testdata/nats_varz.json")
	jsz := mustRead(t, "testdata/nats_jsz.json")

	got, err := ParseNatsMonitor("probe", varz, []byte("not json"), jsz)
	if err != nil {
		t.Fatalf("unexpected error: %v", err)
	}
	// fallback a varz.connections (= 1).
	if s, ok := findNatsSample(got, "nats_connections"); !ok || s.Value != 1 {
		t.Errorf("nats_connections = %v ok=%v, want 1 (fallback varz.connections)", s.Value, ok)
	}
	// jsz sigue vivo.
	if s, ok := findNatsSample(got, "nats_jetstream_streams"); !ok || s.Value != 3 {
		t.Errorf("nats_jetstream_streams = %v ok=%v, want 3", s.Value, ok)
	}
}

// error path: varz inválido devuelve error no-nil (es el core, sin él no hay nada).
func TestParseNatsMonitorInvalidVarz(t *testing.T) {
	if _, err := ParseNatsMonitor("probe", []byte("{{{"), nil, nil); err == nil {
		t.Fatal("expected error for invalid varz, got nil")
	}
}
