// Licensed to Apache Software Foundation (ASF) under one or more contributor
// license agreements. See the NOTICE file distributed with
// this work for additional information regarding copyright
// ownership. Apache Software Foundation (ASF) licenses this file to you 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.

package services

import (
	"github.com/apache/skywalking-banyandb/banyand/observability"
	"github.com/apache/skywalking-banyandb/pkg/meter"
	"github.com/apache/skywalking-banyandb/pkg/meter/native"
)

// factory is the concrete implementation of observability.Factory interface.
type factory struct {
	PromProvider   meter.Provider
	NativeProvider meter.Provider
	NCollection    *native.MetricCollection
}

// NewFactory creates a new factory instance.
// The nCollection parameter should be *native.MetricCollection or nil.
func NewFactory(promProvider, nativeProvider meter.Provider, nCollection interface{}) observability.Factory {
	var nCol *native.MetricCollection
	if nCollection != nil {
		nCol = nCollection.(*native.MetricCollection)
	}
	return &factory{
		PromProvider:   promProvider,
		NativeProvider: nativeProvider,
		NCollection:    nCol,
	}
}

type counterCollection struct {
	counters []meter.Counter
}

// NewCounter init and return the counterCollection.
func (f *factory) NewCounter(name string, labelNames ...string) meter.Counter {
	var counters []meter.Counter
	if f.PromProvider != nil {
		counters = append(counters, f.PromProvider.Counter(name, labelNames...))
	}
	if f.NativeProvider != nil {
		counter := f.NativeProvider.Counter(name, labelNames...)
		f.NCollection.AddCollector(counter.(*native.Counter))
		counters = append(counters, counter)
	}
	return &counterCollection{
		counters: counters,
	}
}

func (c *counterCollection) Inc(delta float64, labelValues ...string) {
	for _, counter := range c.counters {
		counter.Inc(delta, labelValues...)
	}
}

func (c *counterCollection) Delete(labelValues ...string) bool {
	success := true
	for _, counter := range c.counters {
		success = success && counter.Delete(labelValues...)
	}
	return success
}

type gaugeCollection struct {
	gauges []meter.Gauge
}

// NewGauge init and return the gaugeCollection.
func (f *factory) NewGauge(name string, labelNames ...string) meter.Gauge {
	var gauges []meter.Gauge
	if f.PromProvider != nil {
		gauges = append(gauges, f.PromProvider.Gauge(name, labelNames...))
	}
	if f.NativeProvider != nil {
		gauge := f.NativeProvider.Gauge(name, labelNames...)
		f.NCollection.AddCollector(gauge.(*native.Gauge))
		gauges = append(gauges, gauge)
	}
	return &gaugeCollection{
		gauges: gauges,
	}
}

func (g *gaugeCollection) Set(value float64, labelValues ...string) {
	for _, gauge := range g.gauges {
		gauge.Set(value, labelValues...)
	}
}

func (g *gaugeCollection) Add(delta float64, labelValues ...string) {
	for _, gauge := range g.gauges {
		gauge.Add(delta, labelValues...)
	}
}

func (g *gaugeCollection) Delete(labelValues ...string) bool {
	success := true
	for _, gauge := range g.gauges {
		success = success && gauge.Delete(labelValues...)
	}
	return success
}

type histogramCollection struct {
	histograms []meter.Histogram
}

// NewHistogram init and return the histogramCollection.
func (f *factory) NewHistogram(name string, buckets meter.Buckets, labelNames ...string) meter.Histogram {
	var histograms []meter.Histogram
	if f.PromProvider != nil {
		histograms = append(histograms, f.PromProvider.Histogram(name, buckets, labelNames...))
	}
	if f.NativeProvider != nil {
		histogram := f.NativeProvider.Histogram(name, buckets, labelNames...)
		f.NCollection.AddCollector(histogram.(*native.Histogram))
		histograms = append(histograms, histogram)
	}
	return &histogramCollection{
		histograms: histograms,
	}
}

func (h *histogramCollection) Observe(value float64, labelValues ...string) {
	for _, histogram := range h.histograms {
		histogram.Observe(value, labelValues...)
	}
}

func (h *histogramCollection) Delete(labelValues ...string) bool {
	success := true
	for _, histogram := range h.histograms {
		success = success && histogram.Delete(labelValues...)
	}
	return success
}

func (f *factory) Close() {
	if f.PromProvider != nil {
		f.PromProvider.Close()
	}
	if f.NativeProvider != nil {
		f.NativeProvider.Close()
	}
}
