package main

import (
	"math"
	"os"
	"strings"
	"testing"
)

func baseline(t *testing.T) Config {
	t.Helper()
	b, err := os.ReadFile("config.json")
	if err != nil {
		t.Fatal(err)
	}
	c, err := decode(strings.NewReader(string(b)))
	if err != nil {
		t.Fatal(err)
	}
	return c
}

func TestBaseline(t *testing.T) {
	e, err := estimate(baseline(t))
	if err != nil {
		t.Fatal(err)
	}
	if e.Series != 31200 || e.SamplesPerDay != 179712000 {
		t.Fatalf("unexpected estimate: %+v", e)
	}
	want := 2000.0 * 8 * 0.1 * 600 * 86400 / (1 << 30)
	if math.Abs(e.TraceRawGiBDay-want) > 1e-9 {
		t.Fatal(e)
	}
}

func TestCardinalityBudget(t *testing.T) {
	c := baseline(t)
	c.LabelDimensions["route"] = 1000000
	_, err := estimate(c)
	if err == nil || err.Error() != "series budget exceeded: need=2600000000 budget=100000" {
		t.Fatal(err)
	}
	t.Log(err)
}

func TestSensitiveLabelRejected(t *testing.T) {
	c := baseline(t)
	c.LabelDimensions["request_id"] = 1000000
	_, err := estimate(c)
	if err == nil || !strings.Contains(err.Error(), `label "request_id" is not allowed`) {
		t.Fatal(err)
	}
	t.Log(err)
}

func TestStrictJSON(t *testing.T) {
	for _, text := range []string{`{"retention_day":7}`, `{} {}`, `{"trace_keep_ratio":"10%"}`} {
		if _, err := decode(strings.NewReader(text)); err == nil {
			t.Fatalf("accepted %s", text)
		}
	}
}

func TestOversizedConfig(t *testing.T) {
	text := "{}" + strings.Repeat(" ", 64<<10) + "{}"
	if _, err := decode(strings.NewReader(text)); err == nil {
		t.Fatal("accepted oversized input")
	}
}

func TestInvalidNumbers(t *testing.T) {
	cases := []struct {
		name   string
		change func(*Config)
	}{
		{"zero interval", func(c *Config) { c.ScrapeSeconds = 0 }},
		{"too many buckets", func(c *Config) { c.FiniteBuckets = 101 }},
		{"bad ratio", func(c *Config) { c.TraceKeepRatio = 1.1 }},
		{"nan ratio", func(c *Config) { c.TraceKeepRatio = math.NaN() }},
		{"infinite size", func(c *Config) { c.BytesPerSpan = math.Inf(1) }},
		{"negative retention", func(c *Config) { c.RetentionDays = -1 }},
		{"integer overflow", func(c *Config) { c.LabelDimensions["route"] = math.MaxUint64 }},
		{"floating overflow", func(c *Config) { c.RequestsPerSec = math.MaxFloat64 }},
	}
	for _, tc := range cases {
		t.Run(tc.name, func(t *testing.T) {
			c := baseline(t)
			tc.change(&c)
			if _, err := estimate(c); err == nil {
				t.Fatal("expected rejection")
			}
		})
	}
}

func TestZeroTraceRetention(t *testing.T) {
	c := baseline(t)
	c.TraceKeepRatio = 0
	e, err := estimate(c)
	if err != nil || e.TraceRawGiBKept != 0 || e.LogRawGiBKept == 0 {
		t.Fatalf("%+v %v", e, err)
	}
}
