package main

import (
	"encoding/hex"
	"fmt"
	"strings"
)

// Span merekam satu unit kerja dalam sebuah trace.
type Span struct {
	TraceID string // 32 karakter heksadesimal, sama untuk satu trace
	SpanID  string // 16 karakter heksadesimal, unik per span
	IndukID string // SpanID induk; kosong untuk span root
	Nama    string
	Layanan string
	Durasi  int    // milidetik
	Status  string // "ok" atau "error"
}

// tampilkanSpan mencetak pohon span: anak mengikuti induknya dengan
// indentasi bertingkat, urutan mengikuti urutan slice.
func tampilkanSpan(spans []Span, indukID string, kedalaman int) {
	for _, s := range spans {
		if s.IndukID != indukID {
			continue
		}
		fmt.Printf("%s- %s (%s, %dms, %s)\n",
			strings.Repeat("  ", kedalaman), s.Nama, s.Layanan, s.Durasi, s.Status)
		tampilkanSpan(spans, s.SpanID, kedalaman+1)
	}
}

// TraceParent adalah komponen header W3C traceparent.
type TraceParent struct {
	Versi   string // 2 karakter heksadesimal
	TraceID string // 32 karakter heksadesimal
	SpanID  string // 16 karakter heksadesimal
	Flag    string // 2 karakter heksadesimal
}

// parseTraceParent memecah header "versi-traceid-spanid-flags" dan
// memvalidasi panjang serta format heksadesimal tiap segmen.
func parseTraceParent(header string) (TraceParent, error) {
	bagian := strings.Split(header, "-")
	if len(bagian) != 4 {
		return TraceParent{}, fmt.Errorf("jumlah segmen %d, harus 4", len(bagian))
	}
	for i, seg := range bagian {
		if _, err := hex.DecodeString(seg); err != nil {
			return TraceParent{}, fmt.Errorf("segmen %d tidak valid: %w", i, err)
		}
	}
	if len(bagian[1]) != 32 {
		return TraceParent{}, fmt.Errorf("trace-id harus 32 karakter, dapat %d", len(bagian[1]))
	}
	if len(bagian[2]) != 16 {
		return TraceParent{}, fmt.Errorf("span-id harus 16 karakter, dapat %d", len(bagian[2]))
	}
	return TraceParent{Versi: bagian[0], TraceID: bagian[1], SpanID: bagian[2], Flag: bagian[3]}, nil
}

// formatTraceParent merakit kembali header dari komponennya.
func formatTraceParent(tp TraceParent) string {
	return strings.Join([]string{tp.Versi, tp.TraceID, tp.SpanID, tp.Flag}, "-")
}

func main() {
	const traceID = "4bf92f3577b34da6a3ce929d0e0e4736"

	// ID span di bawah sengaja tetap agar demo deterministik; produksi
	// memakai crypto/rand (8 byte untuk span, 16 byte untuk trace).
	spans := []Span{
		{TraceID: traceID, SpanID: "bb9cd33a0c521e08", Nama: "POST /pesanan", Layanan: "api", Durasi: 220, Status: "error"},
		{TraceID: traceID, SpanID: "3c5a9f01b27e4d10", IndukID: "bb9cd33a0c521e08", Nama: "validasi", Layanan: "api", Durasi: 12, Status: "ok"},
		{TraceID: traceID, SpanID: "7a2e88d4c1f3b529", IndukID: "bb9cd33a0c521e08", Nama: "AmbilStok", Layanan: "pesanan", Durasi: 95, Status: "ok"},
		{TraceID: traceID, SpanID: "9f01a4c8e2b7d635", IndukID: "7a2e88d4c1f3b529", Nama: "SELECT stok", Layanan: "db", Durasi: 40, Status: "ok"},
		{TraceID: traceID, SpanID: "e58b2c7f9a1d4e63", IndukID: "bb9cd33a0c521e08", Nama: "Tagih", Layanan: "pembayaran", Durasi: 85, Status: "error"},
	}

	fmt.Println("--- pohon span satu trace ---")
	fmt.Printf("trace %s — %d span\n", traceID, len(spans))
	tampilkanSpan(spans, "", 0)

	fmt.Println("--- parse dan format header traceparent ---")
	masuk := "00-" + traceID + "-00f067aa0ba902b7-01"
	tp, err := parseTraceParent(masuk)
	if err != nil {
		panic(err)
	}
	fmt.Println("masuk    :", masuk)
	fmt.Printf("versi=%s trace-id=%s span-id=%s flag=%s\n", tp.Versi, tp.TraceID, tp.SpanID, tp.Flag)
	fmt.Println("round-trip identik:", formatTraceParent(tp) == masuk)

	// Layanan penerima membuat span baru; request keluar membawa span ID
	// miliknya sebagai induk bagi layanan berikutnya.
	keluar := formatTraceParent(TraceParent{
		Versi:   "00",
		TraceID: tp.TraceID,
		SpanID:  "85e1c2b7d90f4a16",
		Flag:    "01",
	})
	fmt.Println("keluar   :", keluar)

	fmt.Println("--- header tidak valid ---")
	_, err = parseTraceParent("00-XYZ-00f067aa0ba902b7-01")
	fmt.Println("galat    :", err)
}
