// Copyright 2025 PingCAP, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

//go:build vm_histogram

package promutil

import (
	"fmt"
	"strings"
	"sync"

	vmetrics "github.com/VictoriaMetrics/metrics"
	"github.com/prometheus/client_golang/prometheus"
	dto "github.com/prometheus/client_model/go"
)

type vmHistogram struct {
	vmHist *vmetrics.Histogram
	desc   *prometheus.Desc
}

func (h *vmHistogram) Observe(v float64) {
	h.vmHist.Update(v)
}

func (h *vmHistogram) Desc() *prometheus.Desc {
	return h.desc
}

func (h *vmHistogram) Write(out *dto.Metric) error {
	out.Histogram = &dto.Histogram{}
	return nil
}

func (h *vmHistogram) Describe(ch chan<- *prometheus.Desc) {
	ch <- h.desc
}

func (h *vmHistogram) Collect(ch chan<- prometheus.Metric) {
	// Do nothing - VM histogram metrics are exported via vmetrics.WritePrometheus
}

type vmHistogramVec struct {
	opts       prometheus.HistogramOpts
	labelNames []string
	mu         sync.RWMutex
	histograms map[string]*vmHistogram
}

func (hv *vmHistogramVec) With(labels prometheus.Labels) prometheus.Observer {
	lvs := make([]string, len(hv.labelNames))
	for i, name := range hv.labelNames {
		lvs[i] = labels[name]
	}
	return hv.WithLabelValues(lvs...)
}

func (hv *vmHistogramVec) WithLabelValues(lvs ...string) prometheus.Observer {
	if len(lvs) != len(hv.labelNames) {
		panic(fmt.Sprintf("label values count mismatch: got %d, want %d", len(lvs), len(hv.labelNames)))
	}

	key := strings.Join(lvs, "\xff")
	hv.mu.RLock()
	h, ok := hv.histograms[key]
	hv.mu.RUnlock()

	if ok {
		return h
	}

	hv.mu.Lock()
	defer hv.mu.Unlock()

	if h, ok := hv.histograms[key]; ok {
		return h
	}

	parts := []string{hv.opts.Namespace, hv.opts.Subsystem, hv.opts.Name}
	name := strings.Join(parts, "_")

	labelPairs := make([]string, 0, len(hv.labelNames)+len(hv.opts.ConstLabels))
	for i, name := range hv.labelNames {
		labelPairs = append(labelPairs, fmt.Sprintf(`%s="%s"`, name, lvs[i]))
	}
	for k, v := range hv.opts.ConstLabels {
		labelPairs = append(labelPairs, fmt.Sprintf(`%s="%s"`, k, v))
	}
	if len(labelPairs) > 0 {
		name = fmt.Sprintf("%s{%s}", name, strings.Join(labelPairs, ","))
	}

	vmHist := vmetrics.GetOrCreateHistogram(name)

	// NOTE: Prometheus const labels must NOT be included in variable labelNames.
	// Otherwise prometheus.NewDesc will panic due to label name collisions.
	labelNames := hv.labelNames

	desc := prometheus.NewDesc(
		prometheus.BuildFQName(hv.opts.Namespace, hv.opts.Subsystem, hv.opts.Name),
		hv.opts.Help,
		labelNames,
		hv.opts.ConstLabels,
	)

	h = &vmHistogram{
		vmHist: vmHist,
		desc:   desc,
	}

	hv.histograms[key] = h
	return h
}

func (hv *vmHistogramVec) Describe(ch chan<- *prometheus.Desc) {
	hv.mu.RLock()
	defer hv.mu.RUnlock()
	for _, h := range hv.histograms {
		ch <- h.desc
	}
}

func (hv *vmHistogramVec) Collect(ch chan<- prometheus.Metric) {
	// Do nothing - VM histogram metrics are exported via vmetrics.WritePrometheus
}

func (defaultFactory) NewHistogram(opts prometheus.HistogramOpts) Histogram {
	parts := []string{opts.Namespace, opts.Subsystem, opts.Name}
	name := strings.Join(parts, "_")

	if len(opts.ConstLabels) > 0 {
		labelPairs := make([]string, 0, len(opts.ConstLabels))
		for k, v := range opts.ConstLabels {
			labelPairs = append(labelPairs, fmt.Sprintf(`%s="%s"`, k, v))
		}
		name = fmt.Sprintf("%s{%s}", name, strings.Join(labelPairs, ","))
	}

	vmHist := vmetrics.GetOrCreateHistogram(name)

	desc := prometheus.NewDesc(
		prometheus.BuildFQName(opts.Namespace, opts.Subsystem, opts.Name),
		opts.Help,
		nil, // No variable labels for single histogram.
		opts.ConstLabels,
	)

	return &vmHistogram{
		vmHist: vmHist,
		desc:   desc,
	}
}

func (defaultFactory) NewHistogramVec(opts prometheus.HistogramOpts, labelNames []string) HistogramVec {
	return &vmHistogramVec{
		opts:       opts,
		labelNames: labelNames,
		histograms: make(map[string]*vmHistogram),
	}
}

// RegisterHistogram is a no-op for vm_histogram builds. Histogram metrics
// are exported via VictoriaMetrics and do not need registration with Prometheus.
func RegisterHistogram(h Histogram) {
}

// RegisterHistogramVec is a no-op for vm_histogram builds. Histogram vectors
// are exported via VictoriaMetrics and do not need registration with Prometheus.
func RegisterHistogramVec(h HistogramVec) {
}

// AsPrometheusHistogramVec converts a HistogramVec to *prometheus.HistogramVec.
// For VictoriaMetrics build, this returns nil as we use a different histogram implementation.
func AsPrometheusHistogramVec(h HistogramVec) *prometheus.HistogramVec {
	// In VM mode, we don't have prometheus.HistogramVec, return nil
	// Callers should check for nil and handle accordingly
	return nil
}
