package main

import (
	"fmt"
	"maps"
	"slices"
	"strings"
	"sync"
)

// Metrik adalah kontrak render: setiap jenis metrik tahu cara mencetak
// dirinya sendiri menjadi baris "nama nilai".
type Metrik interface {
	Baris(nama string) []string
}

// Counter adalah metrik monoton: nilainya hanya naik, tidak pernah turun.
type Counter struct {
	mu    sync.Mutex
	nilai map[string]float64
}

// Inc menaikkan counter satu poin untuk satu kombinasi label.
func (c *Counter) Inc(label string) {
	c.mu.Lock()
	defer c.mu.Unlock()
	if c.nilai == nil {
		c.nilai = map[string]float64{}
	}
	c.nilai[label]++
}

// Baris mencetak setiap label terurut supaya output deterministik.
func (c *Counter) Baris(nama string) []string {
	c.mu.Lock()
	defer c.mu.Unlock()
	var hasil []string
	for _, k := range slices.Sorted(maps.Keys(c.nilai)) {
		hasil = append(hasil, fmt.Sprintf("%s{%s} %g", nama, k, c.nilai[k]))
	}
	return hasil
}

// Gauge adalah metrik bebas: nilainya bisa naik maupun turun.
type Gauge struct {
	mu    sync.Mutex
	nilai float64
}

// Set menimpa nilai gauge dengan nilai sesaat.
func (g *Gauge) Set(v float64) {
	g.mu.Lock()
	defer g.mu.Unlock()
	g.nilai = v
}

func (g *Gauge) Baris(nama string) []string {
	g.mu.Lock()
	defer g.mu.Unlock()
	return []string{fmt.Sprintf("%s %g", nama, g.nilai)}
}

// Histogram merekam distribusi nilai; kuantil dihitung saat render.
type Histogram struct {
	mu   sync.Mutex
	data []float64
}

// Observe menambahkan satu observasi ke histogram.
func (h *Histogram) Observe(v float64) {
	h.mu.Lock()
	defer h.mu.Unlock()
	h.data = append(h.data, v)
}

func (h *Histogram) Baris(nama string) []string {
	h.mu.Lock()
	data := slices.Clone(h.data)
	h.mu.Unlock()
	slices.Sort(data)
	return []string{
		fmt.Sprintf("%s_jumlah %d", nama, len(data)),
		fmt.Sprintf("%s_p50 %.1f", nama, kuantil(data, 0.50)),
		fmt.Sprintf("%s_p90 %.1f", nama, kuantil(data, 0.90)),
		fmt.Sprintf("%s_p99 %.1f", nama, kuantil(data, 0.99)),
	}
}

// kuantil menghitung kuantil q dari data terurut dengan interpolasi linear.
func kuantil(data []float64, q float64) float64 {
	if len(data) == 0 {
		return 0
	}
	pos := q * float64(len(data)-1)
	bawah := int(pos)
	atas := bawah + 1
	if atas >= len(data) {
		return data[len(data)-1]
	}
	frac := pos - float64(bawah)
	return data[bawah] + frac*(data[atas]-data[bawah])
}

// Registry menyimpan metrik in-memory dan mencetaknya terurut nama.
type Registry struct {
	mu     sync.Mutex
	metrik map[string]Metrik
}

// Daftarkan mendaftarkan satu metrik di bawah sebuah nama.
func (r *Registry) Daftarkan(nama string, m Metrik) {
	r.mu.Lock()
	defer r.mu.Unlock()
	if r.metrik == nil {
		r.metrik = map[string]Metrik{}
	}
	r.metrik[nama] = m
}

// Render mencetak seluruh metrik terurut nama.
func (r *Registry) Render() {
	for _, nama := range slices.Sorted(maps.Keys(r.metrik)) {
		for _, b := range r.metrik[nama].Baris(nama) {
			fmt.Println(b)
		}
	}
}

// labelKunci merangkai pasangan kunci-nilai menjadi kunci label tunggal.
func labelKunci(pasangan ...string) string {
	var sb strings.Builder
	for i := 0; i+1 < len(pasangan); i += 2 {
		fmt.Fprintf(&sb, "%s=%q", pasangan[i], pasangan[i+1])
		if i+2 < len(pasangan) {
			sb.WriteByte(',')
		}
	}
	return sb.String()
}

func main() {
	reg := &Registry{}

	// Counter: jumlah request per kombinasi metode dan kode status.
	requests := &Counter{}
	reg.Daftarkan("http_requests_total", requests)
	for range 3 {
		requests.Inc(labelKunci("metode", "GET", "kode", "200"))
	}
	requests.Inc(labelKunci("metode", "GET", "kode", "500"))
	for range 2 {
		requests.Inc(labelKunci("metode", "POST", "kode", "201"))
	}
	fmt.Println("--- counter http_requests_total ---")
	for _, b := range requests.Baris("http_requests_total") {
		fmt.Println(b)
	}

	// Gauge: ukuran antrian pesanan yang berubah setiap saat.
	antrian := &Gauge{}
	reg.Daftarkan("antrian_pesanan", antrian)
	fmt.Println("--- gauge antrian_pesanan ---")
	antrian.Set(5)
	for _, b := range antrian.Baris("antrian_pesanan") {
		fmt.Println("nilai awal:", b)
	}
	antrian.Set(3) // dua pesanan selesai diproses
	for _, b := range antrian.Baris("antrian_pesanan") {
		fmt.Println("setelah 2 diproses:", b)
	}

	// Histogram: durasi request dalam milidetik.
	durasi := &Histogram{}
	reg.Daftarkan("durasi_request_ms", durasi)
	for _, v := range []float64{42, 1180, 65, 230, 87, 95, 110, 61, 78, 99} {
		durasi.Observe(v)
	}

	fmt.Println("--- registry lengkap (terurut nama) ---")
	reg.Render()
}
