package metrics

import (
	"fmt"
	"math"
	"net/http"
	"runtime"
	"sort"
	"strconv"
	"strings"
	"sync"
	"time"
)

var processStartedAt = time.Now()

type Labels map[string]string

type Registry struct {
	mu         sync.RWMutex
	counters   map[string]map[string]float64
	gauges     map[string]map[string]float64
	histograms map[string]*histogramFamily
}

type histogramFamily struct {
	buckets map[string]map[float64]float64
	counts  map[string]float64
	sums    map[string]float64
}

func NewRegistry() *Registry {
	return &Registry{
		counters:   map[string]map[string]float64{},
		gauges:     map[string]map[string]float64{},
		histograms: map[string]*histogramFamily{},
	}
}

func RegisterServiceInfo(registry *Registry, service string) {
	if registry == nil {
		return
	}
	service = strings.TrimSpace(service)
	if service == "" {
		service = "vehicle-service"
	}
	registry.SetGauge("vehicle_service_info", Labels{"service": service}, 1)
	RecordProcessRuntime(registry)
}

func RecordProcessRuntime(registry *Registry) {
	if registry == nil {
		return
	}
	var mem runtime.MemStats
	runtime.ReadMemStats(&mem)
	registry.SetGauge("vehicle_process_start_time_unix_seconds", nil, float64(processStartedAt.Unix()))
	registry.SetGauge("vehicle_process_uptime_seconds", nil, time.Since(processStartedAt).Seconds())
	registry.SetGauge("vehicle_process_heap_alloc_bytes", nil, float64(mem.HeapAlloc))
	registry.SetGauge("vehicle_process_heap_sys_bytes", nil, float64(mem.HeapSys))
	registry.SetGauge("vehicle_process_goroutines", nil, float64(runtime.NumGoroutine()))
}

func RegisterKafkaConsumerInfo(registry *Registry, service string, group string, topics []string) {
	if registry == nil {
		return
	}
	service = strings.TrimSpace(service)
	if service == "" {
		service = "vehicle-service"
	}
	group = strings.TrimSpace(group)
	if group == "" {
		return
	}
	for _, topic := range topics {
		topic = strings.TrimSpace(topic)
		if topic == "" {
			continue
		}
		registry.SetGauge("vehicle_kafka_consumer_info", Labels{
			"service": service,
			"group":   group,
			"topic":   topic,
		}, 1)
	}
}

func (r *Registry) IncCounter(name string, labels Labels) {
	r.AddCounter(name, labels, 1)
}

func (r *Registry) AddCounter(name string, labels Labels, value float64) {
	name = strings.TrimSpace(name)
	if r == nil || name == "" || value == 0 {
		return
	}
	key := labelsKey(labels)
	r.mu.Lock()
	defer r.mu.Unlock()
	if r.counters[name] == nil {
		r.counters[name] = map[string]float64{}
	}
	r.counters[name][key] += value
}

func (r *Registry) SetGauge(name string, labels Labels, value float64) {
	name = strings.TrimSpace(name)
	if r == nil || name == "" {
		return
	}
	key := labelsKey(labels)
	r.mu.Lock()
	defer r.mu.Unlock()
	if r.gauges[name] == nil {
		r.gauges[name] = map[string]float64{}
	}
	r.gauges[name][key] = value
}

func (r *Registry) AddGauge(name string, labels Labels, value float64) {
	name = strings.TrimSpace(name)
	if r == nil || name == "" || value == 0 {
		return
	}
	key := labelsKey(labels)
	r.mu.Lock()
	defer r.mu.Unlock()
	if r.gauges[name] == nil {
		r.gauges[name] = map[string]float64{}
	}
	r.gauges[name][key] += value
}

func (r *Registry) SetKafkaLag(name string, topic string, partition int, offset int64, highWaterMark int64) {
	lag := highWaterMark - offset - 1
	if lag < 0 {
		lag = 0
	}
	r.SetGauge(name, Labels{
		"topic":     topic,
		"partition": strconv.Itoa(partition),
	}, float64(lag))
}

func RecordLastActivity(registry *Registry, name string, labels Labels) {
	if registry == nil {
		return
	}
	registry.SetGauge(name, labels, float64(time.Now().Unix()))
}

func (r *Registry) ObserveHistogram(name string, labels Labels, buckets []float64, value float64) {
	name = strings.TrimSpace(name)
	if r == nil || name == "" {
		return
	}
	key := labelsKey(labels)
	r.mu.Lock()
	defer r.mu.Unlock()
	family := r.histograms[name]
	if family == nil {
		family = &histogramFamily{
			buckets: map[string]map[float64]float64{},
			counts:  map[string]float64{},
			sums:    map[string]float64{},
		}
		r.histograms[name] = family
	}
	if family.buckets[key] == nil {
		family.buckets[key] = map[float64]float64{}
	}
	normalizedBuckets := append([]float64(nil), buckets...)
	sort.Float64s(normalizedBuckets)
	for _, bucket := range normalizedBuckets {
		if value <= bucket {
			family.buckets[key][bucket]++
		} else if _, ok := family.buckets[key][bucket]; !ok {
			family.buckets[key][bucket] = 0
		}
	}
	family.buckets[key][math.Inf(1)]++
	family.counts[key]++
	family.sums[key] += value
}

func (r *Registry) Render() string {
	if r == nil {
		return ""
	}
	r.mu.RLock()
	defer r.mu.RUnlock()

	var names []string
	for name := range r.counters {
		names = append(names, name)
	}
	sort.Strings(names)

	var b strings.Builder
	for _, name := range names {
		renderMetricFamily(&b, name, "counter", r.counters[name])
	}

	names = names[:0]
	for name := range r.gauges {
		names = append(names, name)
	}
	sort.Strings(names)
	for _, name := range names {
		renderMetricFamily(&b, name, "gauge", r.gauges[name])
	}

	names = names[:0]
	for name := range r.histograms {
		names = append(names, name)
	}
	sort.Strings(names)
	for _, name := range names {
		renderHistogramFamily(&b, name, r.histograms[name])
	}
	return b.String()
}

func renderMetricFamily(b *strings.Builder, name string, metricType string, values map[string]float64) {
	b.WriteString("# TYPE ")
	b.WriteString(name)
	b.WriteString(" ")
	b.WriteString(metricType)
	b.WriteString("\n")
	var keys []string
	for key := range values {
		keys = append(keys, key)
	}
	sort.Strings(keys)
	for _, key := range keys {
		b.WriteString(name)
		if key != "" {
			b.WriteString("{")
			b.WriteString(key)
			b.WriteString("}")
		}
		b.WriteString(" ")
		b.WriteString(formatNumber(values[key]))
		b.WriteString("\n")
	}
}

func renderHistogramFamily(b *strings.Builder, name string, family *histogramFamily) {
	if family == nil {
		return
	}
	b.WriteString("# TYPE ")
	b.WriteString(name)
	b.WriteString(" histogram\n")
	var keys []string
	for key := range family.counts {
		keys = append(keys, key)
	}
	sort.Strings(keys)
	for _, key := range keys {
		var buckets []float64
		for bucket := range family.buckets[key] {
			buckets = append(buckets, bucket)
		}
		sort.Float64s(buckets)
		for _, bucket := range buckets {
			renderHistogramMetric(b, name+"_bucket", key, "le", bucketLabel(bucket), family.buckets[key][bucket])
		}
		renderMetricSample(b, name+"_count", key, family.counts[key])
		renderMetricSample(b, name+"_sum", key, family.sums[key])
	}
}

func renderHistogramMetric(b *strings.Builder, name string, key string, extraLabel string, extraValue string, value float64) {
	b.WriteString(name)
	labels := mergeLabelKey(key, extraLabel, extraValue)
	if labels != "" {
		b.WriteString("{")
		b.WriteString(labels)
		b.WriteString("}")
	}
	b.WriteString(" ")
	b.WriteString(formatNumber(value))
	b.WriteString("\n")
}

func renderMetricSample(b *strings.Builder, name string, key string, value float64) {
	b.WriteString(name)
	if key != "" {
		b.WriteString("{")
		b.WriteString(key)
		b.WriteString("}")
	}
	b.WriteString(" ")
	b.WriteString(formatNumber(value))
	b.WriteString("\n")
}

func mergeLabelKey(key string, extraLabel string, extraValue string) string {
	extra := fmt.Sprintf(`%s="%s"`, sanitizeLabelName(extraLabel), escapeLabelValue(extraValue))
	if key == "" {
		return extra
	}
	return extra + "," + key
}

func bucketLabel(value float64) string {
	if math.IsInf(value, 1) {
		return "+Inf"
	}
	return formatNumber(value)
}

func NewHandler(registry *Registry) http.Handler {
	if registry == nil {
		registry = NewRegistry()
	}
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		if r.Method != http.MethodGet {
			http.Error(w, "method not allowed", http.StatusMethodNotAllowed)
			return
		}
		if strings.Trim(r.URL.Path, "/") != "metrics" {
			http.NotFound(w, r)
			return
		}
		RecordProcessRuntime(registry)
		w.Header().Set("Content-Type", "text/plain; version=0.0.4; charset=utf-8")
		_, _ = w.Write([]byte(registry.Render()))
	})
}

func labelsKey(labels Labels) string {
	if len(labels) == 0 {
		return ""
	}
	keys := make([]string, 0, len(labels))
	for key := range labels {
		keys = append(keys, key)
	}
	sort.Strings(keys)
	parts := make([]string, 0, len(keys))
	for _, key := range keys {
		value := labels[key]
		parts = append(parts, fmt.Sprintf(`%s="%s"`, sanitizeLabelName(key), escapeLabelValue(value)))
	}
	return strings.Join(parts, ",")
}

func sanitizeLabelName(value string) string {
	value = strings.TrimSpace(value)
	if value == "" {
		return "label"
	}
	var b strings.Builder
	for i, r := range value {
		ok := r == '_' || (r >= 'a' && r <= 'z') || (r >= 'A' && r <= 'Z') || (i > 0 && r >= '0' && r <= '9')
		if ok {
			b.WriteRune(r)
		} else {
			b.WriteByte('_')
		}
	}
	return b.String()
}

func escapeLabelValue(value string) string {
	value = strings.ReplaceAll(value, `\`, `\\`)
	value = strings.ReplaceAll(value, "\n", `\n`)
	return strings.ReplaceAll(value, `"`, `\"`)
}

func formatNumber(value float64) string {
	if value == float64(int64(value)) {
		return fmt.Sprintf("%d", int64(value))
	}
	return fmt.Sprintf("%g", value)
}
