package documents

import (
	"strings"
	"sync"
	"time"

	"github.com/prometheus/client_golang/prometheus"
	"github.com/prometheus/client_golang/prometheus/promauto"
	documentsv1 "github.com/tech/sendico/pkg/proto/billing/documents/v1"
	"google.golang.org/grpc/codes"
	"google.golang.org/grpc/status"
)

var (
	metricsOnce sync.Once

	requestsTotal  *prometheus.CounterVec
	requestLatency *prometheus.HistogramVec
	batchSize      prometheus.Histogram
	documentBytes  *prometheus.HistogramVec
)

func initMetrics() {
	metricsOnce.Do(func() {
		requestsTotal = promauto.NewCounterVec(
			prometheus.CounterOpts{
				Namespace: "billing",
				Subsystem: "documents",
				Name:      "requests_total",
				Help:      "Total number of billing document requests processed.",
			},
			[]string{"call", "status", "doc_type"},
		)

		requestLatency = promauto.NewHistogramVec(
			prometheus.HistogramOpts{
				Namespace: "billing",
				Subsystem: "documents",
				Name:      "request_latency_seconds",
				Help:      "Latency of billing document requests.",
				Buckets:   prometheus.DefBuckets,
			},
			[]string{"call", "status", "doc_type"},
		)

		batchSize = promauto.NewHistogram(
			prometheus.HistogramOpts{
				Namespace: "billing",
				Subsystem: "documents",
				Name:      "batch_size",
				Help:      "Number of payment references in batch resolution requests.",
				Buckets:   []float64{0, 1, 2, 5, 10, 20, 50, 100, 250, 500},
			},
		)

		documentBytes = promauto.NewHistogramVec(
			prometheus.HistogramOpts{
				Namespace: "billing",
				Subsystem: "documents",
				Name:      "document_bytes",
				Help:      "Size of generated billing document payloads.",
				Buckets:   prometheus.ExponentialBuckets(1024, 2, 10),
			},
			[]string{"doc_type"},
		)
	})
}

func observeRequest(call string, docType documentsv1.DocumentType, statusLabel string, took time.Duration) {
	typeLabel := docTypeLabel(docType)
	requestsTotal.WithLabelValues(call, statusLabel, typeLabel).Inc()
	requestLatency.WithLabelValues(call, statusLabel, typeLabel).Observe(took.Seconds())
}

func observeBatchSize(size int) {
	batchSize.Observe(float64(size))
}

func observeDocumentBytes(docType documentsv1.DocumentType, size int) {
	documentBytes.WithLabelValues(docTypeLabel(docType)).Observe(float64(size))
}

func statusFromError(err error) string {
	if err == nil {
		return "success"
	}
	st, ok := status.FromError(err)
	if !ok {
		return "error"
	}
	code := st.Code()
	if code == codes.OK {
		return "success"
	}
	return strings.ToLower(code.String())
}

func docTypeLabel(docType documentsv1.DocumentType) string {
	label := docType.String()
	if label == "" {
		return "DOCUMENT_TYPE_UNSPECIFIED"
	}
	return label
}
