package holepunch

import (
	
	
	ma 
	
)

const metricNamespace = "libp2p_holepunch"

var (
	directDialsTotal = prometheus.NewCounterVec(
		prometheus.CounterOpts{
			Namespace: metricNamespace,
			Name:      "direct_dials_total",
			Help:      "Direct Dials Total",
		},
		[]string{"outcome"},
	)
	hpAddressOutcomesTotal = prometheus.NewCounterVec(
		prometheus.CounterOpts{
			Namespace: metricNamespace,
			Name:      "address_outcomes_total",
			Help:      "Hole Punch outcomes by Transport",
		},
		[]string{"side", "num_attempts", "ipv", "transport", "outcome"},
	)
	hpOutcomesTotal = prometheus.NewCounterVec(
		prometheus.CounterOpts{
			Namespace: metricNamespace,
			Name:      "outcomes_total",
			Help:      "Hole Punch outcomes overall",
		},
		[]string{"side", "num_attempts", "outcome"},
	)

	collectors = []prometheus.Collector{
		directDialsTotal,
		hpAddressOutcomesTotal,
		hpOutcomesTotal,
	}
)

type MetricsTracer interface {
	HolePunchFinished(side string, attemptNum int, theirAddrs []ma.Multiaddr, ourAddr []ma.Multiaddr, directConn network.ConnMultiaddrs)
	DirectDialFinished(success bool)
}

type metricsTracer struct{}

var _ MetricsTracer = &metricsTracer{}

type metricsTracerSetting struct {
	reg prometheus.Registerer
}

type MetricsTracerOption func(*metricsTracerSetting)

func ( prometheus.Registerer) MetricsTracerOption {
	return func( *metricsTracerSetting) {
		if  != nil {
			.reg = 
		}
	}
}

func ( ...MetricsTracerOption) MetricsTracer {
	 := &metricsTracerSetting{reg: prometheus.DefaultRegisterer}
	for ,  := range  {
		()
	}
	metricshelper.RegisterCollectors(.reg, collectors...)
	// initialise metrics's labels so that the first data point is handled correctly
	for ,  := range []string{"initiator", "receiver"} {
		for ,  := range []string{"1", "2", "3", "4"} {
			for ,  := range []string{"success", "failed", "cancelled", "no_suitable_address"} {
				for ,  := range []string{"ip4", "ip6"} {
					for ,  := range []string{"quic", "quic-v1", "tcp", "webtransport"} {
						hpAddressOutcomesTotal.WithLabelValues(, , , , )
					}
				}
				if  == "cancelled" {
					// not a valid outcome for the overall holepunch metric
					continue
				}
				hpOutcomesTotal.WithLabelValues(, , )
			}
		}
	}
	return &metricsTracer{}
}

// HolePunchFinished tracks metrics completion of a holepunch. Metrics are tracked on
// a holepunch attempt level and on individual addresses involved in a holepunch.
//
// outcome for an address is computed as:
//
//   - success:
//     A direct connection was established with the peer using this address
//   - cancelled:
//     A direct connection was established with the peer but not using this address
//   - failed:
//     No direct connection was made to the peer and the peer reported an address
//     with the same transport as this address
//   - no_suitable_address:
//     The peer reported no address with the same transport as this address
func ( *metricsTracer) ( string,  int,
	 []ma.Multiaddr,  []ma.Multiaddr,  network.ConnMultiaddrs) {
	 := metricshelper.GetStringSlice()
	defer metricshelper.PutStringSlice()

	* = append(*, , getNumAttemptString())
	var ,  string
	if  != nil {
		 = metricshelper.GetIPVersion(.LocalMultiaddr())
		 = metricshelper.GetTransport(.LocalMultiaddr())
	}

	 := 0
	// calculate holepunch outcome for all the addresses involved
	for ,  := range  {
		 := metricshelper.GetIPVersion()
		 := metricshelper.GetTransport()

		 := false
		for ,  := range  {
			 := metricshelper.GetIPVersion()
			 := metricshelper.GetTransport()
			if  ==  &&  ==  {
				// the peer reported an address with the same transport
				 = true
				++

				* = append(*, , )
				if  != nil &&  ==  &&  ==  {
					// the connection was made using this address
					* = append(*, "success")
				} else if  != nil {
					// connection was made but not using this address
					* = append(*, "cancelled")
				} else {
					// no connection was made
					* = append(*, "failed")
				}
				hpAddressOutcomesTotal.WithLabelValues(*...).Inc()
				* = (*)[:2] // 2 because we want to keep (side, numAttempts)
				break
			}
		}
		if ! {
			* = append(*, , , "no_suitable_address")
			hpAddressOutcomesTotal.WithLabelValues(*...).Inc()
			* = (*)[:2] // 2 because we want to keep (side, numAttempts)
		}
	}

	 := "failed"
	if  != nil {
		 = "success"
	} else if  == 0 {
		// there were no matching addresses, this attempt was going to fail
		 = "no_suitable_address"
	}

	* = append(*, )
	hpOutcomesTotal.WithLabelValues(*...).Inc()
}

func getNumAttemptString( int) string {
	var  = [...]string{"0", "1", "2", "3", "4", "5"}
	if  > 5 {
		return "> 5"
	}
	return []
}

func ( *metricsTracer) ( bool) {
	 := metricshelper.GetStringSlice()
	defer metricshelper.PutStringSlice()
	if  {
		* = append(*, "success")
	} else {
		* = append(*, "failed")
	}
	directDialsTotal.WithLabelValues(*...).Inc()
}