package main

import (
	"bytes"
	"context"
	"encoding/json"
	"errors"
	"fmt"
	"log/slog"
	"math"
	"slices"
)

// cumulativeBuckets is a teaching model for classic histogram bucket counts.
// It is not a concurrent metric implementation or a PromQL evaluator.
func cumulativeBuckets(bounds, observations []float64) ([]int, error) {
	if !slices.IsSorted(bounds) {
		return nil, errors.New("unordered bounds")
	}
	for i, b := range bounds {
		if math.IsNaN(b) || math.IsInf(b, 0) || (i > 0 && b == bounds[i-1]) {
			return nil, errors.New("invalid bounds")
		}
	}
	counts := make([]int, len(bounds)+1)
	for _, x := range observations {
		if x < 0 || math.IsNaN(x) || math.IsInf(x, 0) {
			return nil, errors.New("invalid duration")
		}
		for i, b := range bounds {
			if x <= b {
				counts[i]++
			}
		}
		counts[len(bounds)]++ // +Inf bucket
	}
	return counts, nil
}
func merge(a, b []int) ([]int, error) {
	if len(a) != len(b) {
		return nil, errors.New("different bucket counts")
	}
	out := make([]int, len(a))
	for i := range a {
		out[i] = a[i] + b[i]
	}
	return out, nil // caller must ensure identical boundaries and units
}
func quantile(xs []float64, q float64) float64 {
	ys := slices.Clone(xs)
	slices.Sort(ys)
	return ys[int(math.Ceil(q*float64(len(ys))))-1] // nearest-rank, inputs fixed here
}
func logs() (string, bool, error) {
	var b bytes.Buffer
	l := slog.New(slog.NewJSONHandler(&b, &slog.HandlerOptions{ReplaceAttr: func(_ []string, a slog.Attr) slog.Attr {
		if a.Key == slog.TimeKey {
			return slog.Attr{}
		}
		return a
	}}))
	l.LogAttrs(context.Background(), slog.LevelError, "dependency failed",
		slog.String("service", "checkout"), slog.String("route", "/orders/{id}"),
		slog.Int("status", 503), slog.String("trace_id", "demo-trace"))
	var m map[string]any
	err := json.Unmarshal(b.Bytes(), &m)
	return b.String(), m["service"] == "checkout" && m["status"] == float64(503), err
}
func main() {
	line, found, err := logs()
	if err != nil {
		panic(err)
	}
	fmt.Print(line)
	fmt.Println("service=checkout AND status=503:", found)
	bounds := []float64{.1, .3, 1}
	a, err := cumulativeBuckets(bounds, []float64{.05, .2, .8})
	if err != nil {
		panic(err)
	}
	b, err := cumulativeBuckets(bounds, []float64{.1, .4, 1.2})
	if err != nil {
		panic(err)
	}
	total, err := merge(a, b)
	if err != nil {
		panic(err)
	}
	fmt.Printf("bounds=[0.1 0.3 1 +Inf] A=%v B=%v total=%v\n", a, b, total)
	fmt.Printf("fraction_le_0.3=%.3f\n", float64(total[1])/float64(total[3]))
	x, y := []float64{1, 1, 1, 1, 1}, []float64{100}
	fmt.Printf("mean_local_p50=%.1f global_p50=%.1f\n", (quantile(x, .5)+quantile(y, .5))/2, quantile(append(slices.Clone(x), y...), .5))
	fmt.Printf("classic_series=%d with_10000_users=%d\n", 20*5*8*(3+3), 20*5*8*(3+3)*10000)
}
