package grpcimp

import (
	"context"
	"strings"
	"sync"
	"time"

	"github.com/prometheus/client_golang/prometheus"
	"google.golang.org/grpc"
	"google.golang.org/grpc/status"
)

var (
	metricsOnce             sync.Once
	grpcServerRequestsTotal *prometheus.CounterVec
	grpcServerLatency       *prometheus.HistogramVec
)

func initPrometheusMetrics() {
	metricsOnce.Do(func() {
		grpcServerRequestsTotal = prometheus.NewCounterVec(
			prometheus.CounterOpts{
				Name: "grpc_server_requests_total",
				Help: "Total number of gRPC requests handled by the server.",
			},
			[]string{"grpc_service", "grpc_method", "grpc_type", "grpc_code"},
		)

		grpcServerLatency = prometheus.NewHistogramVec(
			prometheus.HistogramOpts{
				Name:    "grpc_server_handling_seconds",
				Help:    "Duration of gRPC requests handled by the server.",
				Buckets: prometheus.DefBuckets,
			},
			[]string{"grpc_service", "grpc_method", "grpc_type", "grpc_code"},
		)

		prometheus.MustRegister(grpcServerRequestsTotal, grpcServerLatency)
	})
}

func prometheusUnaryInterceptor() grpc.UnaryServerInterceptor {
	initPrometheusMetrics()

	return func(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (interface{}, error) {
		start := time.Now()
		resp, err := handler(ctx, req)

		recordMetrics(info.FullMethod, "unary", time.Since(start), err)
		return resp, err
	}
}

func prometheusStreamInterceptor() grpc.StreamServerInterceptor {
	initPrometheusMetrics()

	return func(srv interface{}, ss grpc.ServerStream, info *grpc.StreamServerInfo, handler grpc.StreamHandler) error {
		start := time.Now()
		err := handler(srv, ss)

		recordMetrics(info.FullMethod, streamType(info), time.Since(start), err)
		return err
	}
}

func streamType(info *grpc.StreamServerInfo) string {
	if info == nil {
		return "stream"
	}
	if info.IsServerStream && info.IsClientStream {
		return "bidi"
	}
	if info.IsServerStream {
		return "server_stream"
	}
	if info.IsClientStream {
		return "client_stream"
	}
	return "stream"
}

func recordMetrics(fullMethod string, callType string, duration time.Duration, err error) {
	service, method := splitMethod(fullMethod)
	code := status.Code(err).String()

	grpcServerRequestsTotal.WithLabelValues(service, method, callType, code).Inc()
	grpcServerLatency.WithLabelValues(service, method, callType, code).Observe(duration.Seconds())
}

func splitMethod(fullMethod string) (string, string) {
	if fullMethod == "" {
		return "unknown", "unknown"
	}
	if fullMethod[0] == '/' {
		fullMethod = fullMethod[1:]
	}
	parts := strings.Split(fullMethod, "/")
	if len(parts) < 2 {
		return fullMethod, "unknown"
	}
	return parts[0], parts[1]
}
