package basichost

import (
	
	
	
	
	
	
	
	

	
	
	
	
	
	libp2pwebrtc 
	libp2pwebtransport 
	
	ma 
	manet 
	
)

const maxObservedAddrsPerListenAddr = 5

type observedAddrsManager interface {
	OwnObservedAddrs() []ma.Multiaddr
	ObservedAddrsFor(local ma.Multiaddr) []ma.Multiaddr
}

type hostAddrs struct {
	addrs            []ma.Multiaddr
	localAddrs       []ma.Multiaddr
	reachableAddrs   []ma.Multiaddr
	unreachableAddrs []ma.Multiaddr
	unknownAddrs     []ma.Multiaddr
	relayAddrs       []ma.Multiaddr
}

type addrsManager struct {
	bus                      event.Bus
	natManager               NATManager
	addrsFactory             AddrsFactory
	listenAddrs              func() []ma.Multiaddr
	transportForListening    func(ma.Multiaddr) transport.Transport
	observedAddrsManager     observedAddrsManager
	interfaceAddrs           *interfaceAddrsCache
	addrsReachabilityTracker *addrsReachabilityTracker

	// addrsUpdatedChan is notified when addrs change. This is provided by the caller.
	addrsUpdatedChan chan struct{}

	// triggerAddrsUpdateChan is used to trigger an addresses update.
	triggerAddrsUpdateChan chan struct{}
	// triggerReachabilityUpdate is notified when reachable addrs are updated.
	triggerReachabilityUpdate chan struct{}

	hostReachability atomic.Pointer[network.Reachability]

	addrsMx      sync.RWMutex
	currentAddrs hostAddrs

	wg        sync.WaitGroup
	ctx       context.Context
	ctxCancel context.CancelFunc
}

func newAddrsManager(
	 event.Bus,
	 NATManager,
	 AddrsFactory,
	 func() []ma.Multiaddr,
	 func(ma.Multiaddr) transport.Transport,
	 observedAddrsManager,
	 chan struct{},
	 autonatv2Client,
	 bool,
	 prometheus.Registerer,
) (*addrsManager, error) {
	,  := context.WithCancel(context.Background())
	 := &addrsManager{
		bus:                       ,
		listenAddrs:               ,
		transportForListening:     ,
		observedAddrsManager:      ,
		natManager:                ,
		addrsFactory:              ,
		triggerAddrsUpdateChan:    make(chan struct{}, 1),
		triggerReachabilityUpdate: make(chan struct{}, 1),
		addrsUpdatedChan:          ,
		interfaceAddrs:            &interfaceAddrsCache{},
		ctx:                       ,
		ctxCancel:                 ,
	}
	 := network.ReachabilityUnknown
	.hostReachability.Store(&)

	if  != nil {
		var  MetricsTracker
		if  {
			 = newMetricsTracker(withRegisterer())
		}
		.addrsReachabilityTracker = newAddrsReachabilityTracker(, .triggerReachabilityUpdate, nil, )
	}
	return , nil
}

func ( *addrsManager) () error {
	// TODO: add Start method to NATMgr
	if .addrsReachabilityTracker != nil {
		 := .addrsReachabilityTracker.Start()
		if  != nil {
			return fmt.Errorf("error starting addrs reachability tracker: %s", )
		}
	}

	return .startBackgroundWorker()
}

func ( *addrsManager) () {
	.ctxCancel()
	if .natManager != nil {
		 := .natManager.Close()
		if  != nil {
			log.Warnf("error closing natmgr: %s", )
		}
	}
	if .addrsReachabilityTracker != nil {
		 := .addrsReachabilityTracker.Close()
		if  != nil {
			log.Warnf("error closing addrs reachability tracker: %s", )
		}
	}
	.wg.Wait()
}

func ( *addrsManager) () network.Notifiee {
	// Updating addrs in sync provides the nice property that
	// host.Addrs() just after host.Network().Listen(x) will return x
	return &network.NotifyBundle{
		ListenF:      func(network.Network, ma.Multiaddr) { .triggerAddrsUpdate() },
		ListenCloseF: func(network.Network, ma.Multiaddr) { .triggerAddrsUpdate() },
	}
}

func ( *addrsManager) () {
	.updateAddrs(false, nil)
	select {
	case .triggerAddrsUpdateChan <- struct{}{}:
	default:
	}
}

func ( *addrsManager) () error {
	,  := .bus.Subscribe(new(event.EvtAutoRelayAddrsUpdated), eventbus.Name("addrs-manager"))
	if  != nil {
		return fmt.Errorf("error subscribing to auto relay addrs: %s", )
	}

	,  := .bus.Subscribe(new(event.EvtLocalReachabilityChanged), eventbus.Name("addrs-manager"))
	if  != nil {
		 := .Close()
		if  != nil {
			 = fmt.Errorf("error closign autorelaysub: %w", )
		}
		 = fmt.Errorf("error subscribing to autonat reachability: %s", )
		return errors.Join(, )
	}

	,  := .bus.Emitter(new(event.EvtHostReachableAddrsChanged), eventbus.Stateful)
	if  != nil {
		 := .Close()
		if  != nil {
			 = fmt.Errorf("error closing autorelaysub: %w", )
		}
		 := .Close()
		if  != nil {
			 = fmt.Errorf("error closing autonat reachability: %w", )
		}
		 = fmt.Errorf("error subscribing to autonat reachability: %s", )
		return errors.Join(, , )
	}

	var  []ma.Multiaddr
	// update relay addrs in case we're private
	select {
	case  := <-.Out():
		if ,  := .(event.EvtAutoRelayAddrsUpdated);  {
			 = slices.Clone(.RelayAddrs)
		}
	default:
	}

	select {
	case  := <-.Out():
		if ,  := .(event.EvtLocalReachabilityChanged);  {
			.hostReachability.Store(&.Reachability)
		}
	default:
	}
	// update addresses before starting the worker loop. This ensures that any address updates
	// before calling addrsManager.Start are correctly reported after Start returns.
	.updateAddrs(true, )

	.wg.Add(1)
	go .background(, , , )
	return nil
}

func ( *addrsManager) (,  event.Subscription,
	 event.Emitter,  []ma.Multiaddr,
) {
	defer .wg.Done()
	defer func() {
		 := .Close()
		if  != nil {
			log.Warnf("error closing auto relay addrs sub: %s", )
		}
		 = .Close()
		if  != nil {
			log.Warnf("error closing autonat reachability sub: %s", )
		}
	}()

	 := time.NewTicker(addrChangeTickrInterval)
	defer .Stop()
	var  hostAddrs
	for {
		 := .updateAddrs(true, )
		.notifyAddrsChanged(, , )
		 = 
		select {
		case <-.C:
		case <-.triggerAddrsUpdateChan:
		case <-.triggerReachabilityUpdate:
		case  := <-.Out():
			if ,  := .(event.EvtAutoRelayAddrsUpdated);  {
				 = slices.Clone(.RelayAddrs)
			}
		case  := <-.Out():
			if ,  := .(event.EvtLocalReachabilityChanged);  {
				.hostReachability.Store(&.Reachability)
			}
		case <-.ctx.Done():
			return
		}
	}
}

// updateAddrs updates the addresses of the host and returns the new updated
// addrs
func ( *addrsManager) ( bool,  []ma.Multiaddr) hostAddrs {
	// Must lock while doing both recompute and update as this method is called from
	// multiple goroutines.
	.addrsMx.Lock()
	defer .addrsMx.Unlock()

	 := .getLocalAddrs()
	var , ,  []ma.Multiaddr
	if .addrsReachabilityTracker != nil {
		, ,  = .getConfirmedAddrs()
	}
	if ! {
		 = .currentAddrs.relayAddrs
	} else {
		// Copy the callers slice
		 = slices.Clone()
	}
	 := .getAddrs(slices.Clone(), )

	.currentAddrs = hostAddrs{
		addrs:            append(.currentAddrs.addrs[:0], ...),
		localAddrs:       append(.currentAddrs.localAddrs[:0], ...),
		reachableAddrs:   append(.currentAddrs.reachableAddrs[:0], ...),
		unreachableAddrs: append(.currentAddrs.unreachableAddrs[:0], ...),
		unknownAddrs:     append(.currentAddrs.unknownAddrs[:0], ...),
		relayAddrs:       append(.currentAddrs.relayAddrs[:0], ...),
	}

	return hostAddrs{
		localAddrs:       ,
		addrs:            ,
		reachableAddrs:   ,
		unreachableAddrs: ,
		unknownAddrs:     ,
		relayAddrs:       ,
	}
}

func ( *addrsManager) ( event.Emitter, ,  hostAddrs) {
	if areAddrsDifferent(.localAddrs, .localAddrs) {
		log.Debugf("host local addresses updated: %s", .localAddrs)
		if .addrsReachabilityTracker != nil {
			.addrsReachabilityTracker.UpdateAddrs(.localAddrs)
		}
	}
	if areAddrsDifferent(.addrs, .addrs) {
		log.Debugf("host addresses updated: %s", .localAddrs)
		select {
		case .addrsUpdatedChan <- struct{}{}:
		default:
		}
	}

	// We *must* send both reachability changed and addrs changed events from the
	// same goroutine to ensure correct ordering
	// Consider the events:
	// 	- addr x discovered
	// 	- addr x is reachable
	// 	- addr x removed
	// We must send these events in the same order. It'll be confusing for consumers
	// if the reachable event is received after the addr removed event.
	if areAddrsDifferent(.reachableAddrs, .reachableAddrs) ||
		areAddrsDifferent(.unreachableAddrs, .unreachableAddrs) ||
		areAddrsDifferent(.unknownAddrs, .unknownAddrs) {
		log.Debugf("host reachable addrs updated: %s", .localAddrs)
		if  := .Emit(event.EvtHostReachableAddrsChanged{
			Reachable:   slices.Clone(.reachableAddrs),
			Unreachable: slices.Clone(.unreachableAddrs),
			Unknown:     slices.Clone(.unknownAddrs),
		});  != nil {
			log.Errorf("error sending host reachable addrs changed event: %s", )
		}
	}
}

// Addrs returns the node's dialable addresses both public and private.
// If autorelay is enabled and node reachability is private, it returns
// the node's relay addresses and private network addresses.
func ( *addrsManager) () []ma.Multiaddr {
	.addrsMx.RLock()
	 := slices.Clone(.currentAddrs.localAddrs)
	 := slices.Clone(.currentAddrs.relayAddrs)
	.addrsMx.RUnlock()
	return .getAddrs(, )
}

// getAddrs returns the node's dialable addresses. Mutates localAddrs
func ( *addrsManager) ( []ma.Multiaddr,  []ma.Multiaddr) []ma.Multiaddr {
	 := 
	 := .hostReachability.Load()
	if  != nil && * == network.ReachabilityPrivate {
		// Delete public addresses if the node's reachability is private, and we have relay addresses
		if len() > 0 {
			 = slices.DeleteFunc(, manet.IsPublicAddr)
			 = append(, ...)
		}
	}
	// Make a copy. Consumers can modify the slice elements
	 = slices.Clone(.addrsFactory())
	// Add certhashes for the addresses provided by the user via address factory.
	 = .addCertHashes(ma.Unique())
	slices.SortFunc(, func(,  ma.Multiaddr) int { return .Compare() })
	return 
}

// HolePunchAddrs returns all the host's direct public addresses, reachable or unreachable,
// suitable for hole punching.
func ( *addrsManager) () []ma.Multiaddr {
	 := .DirectAddrs()
	 = slices.Clone(.addrsFactory())
	// AllAddrs may ignore observed addresses in favour of NAT mappings.
	// Use both for hole punching.
	if .observedAddrsManager != nil {
		 = append(, .observedAddrsManager.OwnObservedAddrs()...)
	}
	 = ma.Unique()
	return slices.DeleteFunc(, func( ma.Multiaddr) bool { return !manet.IsPublicAddr() })
}

// DirectAddrs returns all the addresses the host is listening on except circuit addresses.
func ( *addrsManager) () []ma.Multiaddr {
	.addrsMx.RLock()
	defer .addrsMx.RUnlock()
	return slices.Clone(.currentAddrs.localAddrs)
}

// ConfirmedAddrs returns all addresses of the host that are reachable from the internet
func ( *addrsManager) () ( []ma.Multiaddr,  []ma.Multiaddr,  []ma.Multiaddr) {
	.addrsMx.RLock()
	defer .addrsMx.RUnlock()
	return slices.Clone(.currentAddrs.reachableAddrs), slices.Clone(.currentAddrs.unreachableAddrs), slices.Clone(.currentAddrs.unknownAddrs)
}

func ( *addrsManager) ( []ma.Multiaddr) (, ,  []ma.Multiaddr) {
	, ,  = .addrsReachabilityTracker.ConfirmedAddrs()
	return removeNotInSource(, ), removeNotInSource(, ), removeNotInSource(, )
}

var p2pCircuitAddr = ma.StringCast("/p2p-circuit")

func ( *addrsManager) () []ma.Multiaddr {
	 := .listenAddrs()
	if len() == 0 {
		return nil
	}

	 := make([]ma.Multiaddr, 0, 8)
	 = .appendPrimaryInterfaceAddrs(, )
	 = .appendNATAddrs(, , .interfaceAddrs.All())

	// Remove "/p2p-circuit" addresses from the list.
	// The p2p-circuit listener reports its address as just /p2p-circuit. This is
	// useless for dialing. Users need to manage their circuit addresses themselves,
	// or use AutoRelay.
	 = slices.DeleteFunc(, func( ma.Multiaddr) bool {
		return .Equal(p2pCircuitAddr)
	})

	// Remove any unspecified address from the list
	 = slices.DeleteFunc(, func( ma.Multiaddr) bool {
		return manet.IsIPUnspecified()
	})

	// Add certhashes for /webrtc-direct, /webtransport, etc addresses discovered
	// using identify.
	 = .addCertHashes()
	 = ma.Unique()
	slices.SortFunc(, func(,  ma.Multiaddr) int { return .Compare() })
	return 
}

// appendPrimaryInterfaceAddrs appends the primary interface addresses to `dst`.
func ( *addrsManager) ( []ma.Multiaddr,  []ma.Multiaddr) []ma.Multiaddr {
	// resolving any unspecified listen addressees to use only the primary
	// interface to avoid advertising too many addresses.
	if ,  := manet.ResolveUnspecifiedAddresses(, .interfaceAddrs.Filtered());  != nil {
		log.Warnw("failed to resolve listen addrs", "error", )
	} else {
		 = append(, ...)
	}
	return 
}

// appendNATAddrs appends the NAT-ed addrs for the listenAddrs. For unspecified listen addrs it appends the
// public address for all the interfaces.
// Inferring WebTransport from QUIC depends on the observed address manager.
//
// TODO: Merge the natmgr and identify.ObservedAddrManager in to one NatMapper module.
func ( *addrsManager) ( []ma.Multiaddr,  []ma.Multiaddr,  []ma.Multiaddr) []ma.Multiaddr {
	var  []ma.Multiaddr
	for ,  := range  {
		var  ma.Multiaddr
		if .natManager != nil {
			 = .natManager.GetMapping()
		}

		// The order of the cases below is important.
		switch {
		case  == nil: // no nat mapping
			 = .appendObservedAddrs(, , )
		case manet.IsIPUnspecified():
			log.Infof("NAT device reported an unspecified IP as it's external address: %s", )
			,  := ma.SplitFirst()
			 = .appendObservedAddrs([:0], , )
			for ,  := range  {
				,  := ma.SplitFirst()
				if  != nil && manet.IsPublicAddr(.Multiaddr()) {
					 = append(, .Encapsulate())
				}
			}
		// This is !Public as opposed to IsPrivate intentionally.
		// Public is a more restrictive classification in some cases, like IPv6 addresses which only
		// consider unicast IPv6 addresses allocated so far as public(2000::/3).
		case !manet.IsPublicAddr(): // nat reported non public addr(maybe CGNAT?)
			// use both NAT and observed addr
			 = append(, )
			 = .appendObservedAddrs(, , )
		default: // public addr
			 = append(, )
		}
	}
	return 
}

func ( *addrsManager) ( []ma.Multiaddr,  ma.Multiaddr,  []ma.Multiaddr) []ma.Multiaddr {
	if .observedAddrsManager == nil {
		return 
	}
	// Add it for the listenAddr first.
	// listenAddr maybe unspecified. That's okay as connections on UDP transports
	// will have the unspecified address as the local address.
	 := .observedAddrsManager.ObservedAddrsFor()
	if len() > maxObservedAddrsPerListenAddr {
		 = [:maxObservedAddrsPerListenAddr]
	}
	 = append(, ...)

	// if it can be resolved into more addresses, add them too
	,  := manet.ResolveUnspecifiedAddress(, )
	if  != nil {
		log.Warnf("failed to resolve listen addr %s, %s: %s", , , )
		return 
	}
	for ,  := range  {
		 = .observedAddrsManager.ObservedAddrsFor()
		if len() > maxObservedAddrsPerListenAddr {
			 = [:maxObservedAddrsPerListenAddr]
		}
		 = append(, ...)
	}
	return 
}

func ( *addrsManager) ( []ma.Multiaddr) []ma.Multiaddr {
	if .transportForListening == nil {
		return 
	}

	// TODO(sukunrt): Move this to swarm.
	// There are two parts to determining our external address
	// 1. From the NAT device, or identify, or other such STUN like mechanism.
	// All that matters here is (internal_ip, internal_port, tcp) => (external_ip, external_port, tcp)
	// The rest of the address should be cut and appended to the external one.
	// 2. The user provides us with the address (/ip4/1.2.3.4/udp/1/webrtc-direct) and we add the certhash.
	// This API should be where the transports are, i.e. swarm.
	//
	// It would have been nice to remove this completely and just work with
	// mapping the interface thinwaist addresses (tcp, 192.168.18.18:4000 => 1.2.3.4:4577)
	// but that is only convenient if we're using the same port for listening on
	// all transports which share the same thinwaist protocol. If you listen
	// on 4001 for tcp, and 4002 for websocket, then it's a terrible API.
	type  interface {
		( ma.Multiaddr) (ma.Multiaddr, bool)
	}

	for ,  := range  {
		,  := libp2pwebtransport.IsWebtransportMultiaddr()
		,  := libp2pwebrtc.IsWebRTCDirectMultiaddr()
		if ( &&  == 0) || ( &&  == 0) {
			 := .transportForListening()
			if  == nil {
				continue
			}
			,  := .()
			if ! {
				continue
			}
			,  := .()
			if ! {
				log.Warnf("Couldn't add certhashes to multiaddr: %s", )
				continue
			}
			[] = 
		}
	}
	return 
}

func areAddrsDifferent(,  []ma.Multiaddr) bool {
	// TODO: make the sorted nature of ma.Unique a guarantee in multiaddrs
	 = ma.Unique()
	 = ma.Unique()
	if len() != len() {
		return true
	}
	slices.SortFunc(, func(,  ma.Multiaddr) int { return .Compare() })
	slices.SortFunc(, func(,  ma.Multiaddr) int { return .Compare() })
	for  := range  {
		if ![].Equal([]) {
			return true
		}
	}
	return false
}

const interfaceAddrsCacheTTL = time.Minute

type interfaceAddrsCache struct {
	mx                     sync.RWMutex
	filtered               []ma.Multiaddr
	all                    []ma.Multiaddr
	updateLocalIPv4Backoff backoff.ExpBackoff
	updateLocalIPv6Backoff backoff.ExpBackoff
	lastUpdated            time.Time
}

func ( *interfaceAddrsCache) () []ma.Multiaddr {
	.mx.RLock()
	if time.Now().After(.lastUpdated.Add(interfaceAddrsCacheTTL)) {
		.mx.RUnlock()
		return .update(true)
	}
	defer .mx.RUnlock()
	return .filtered
}

func ( *interfaceAddrsCache) () []ma.Multiaddr {
	.mx.RLock()
	if time.Now().After(.lastUpdated.Add(interfaceAddrsCacheTTL)) {
		.mx.RUnlock()
		return .update(false)
	}
	defer .mx.RUnlock()
	return .all
}

func ( *interfaceAddrsCache) ( bool) []ma.Multiaddr {
	.mx.Lock()
	defer .mx.Unlock()
	if !time.Now().After(.lastUpdated.Add(interfaceAddrsCacheTTL)) {
		if  {
			return .filtered
		}
		return .all
	}
	.updateUnlocked()
	.lastUpdated = time.Now()
	if  {
		return .filtered
	}
	return .all
}

func ( *interfaceAddrsCache) () {
	.filtered = nil
	.all = nil

	// Try to use the default ipv4/6 addresses.
	// TODO: Remove this. We should advertise all interface addresses.
	if ,  := netroute.New();  != nil {
		log.Debugw("failed to build Router for kernel's routing table", "error", )
	} else {

		var  net.IP
		var  bool
		,  = .updateLocalIPv4Backoff.Run(func() error {
			_, _, ,  = .Route(net.IPv4zero)
			return 
		})

		if  &&  != nil {
			log.Debugw("failed to fetch local IPv4 address", "error", )
		} else if  && .IsGlobalUnicast() {
			,  := manet.FromIP()
			if  == nil {
				.filtered = append(.filtered, )
			}
		}

		var  net.IP
		,  = .updateLocalIPv6Backoff.Run(func() error {
			_, _, ,  = .Route(net.IPv6unspecified)
			return 
		})

		if  &&  != nil {
			log.Debugw("failed to fetch local IPv6 address", "error", )
		} else if  && .IsGlobalUnicast() {
			,  := manet.FromIP()
			if  == nil {
				.filtered = append(.filtered, )
			}
		}
	}

	// Resolve the interface addresses
	,  := manet.InterfaceMultiaddrs()
	if  != nil {
		// This usually shouldn't happen, but we could be in some kind
		// of funky restricted environment.
		log.Errorw("failed to resolve local interface addresses", "error", )

		// Add the loopback addresses to the filtered addrs and use them as the non-filtered addrs.
		// Then bail. There's nothing else we can do here.
		.filtered = append(.filtered, manet.IP4Loopback, manet.IP6Loopback)
		.all = .filtered
		return
	}

	// remove link local ipv6 addresses
	.all = slices.DeleteFunc(, manet.IsIP6LinkLocal)

	// If netroute failed to get us any interface addresses, use all of
	// them.
	if len(.filtered) == 0 {
		// Add all addresses.
		.filtered = .all
	} else {
		// Only add loopback addresses. Filter these because we might
		// not _have_ an IPv6 loopback address.
		for ,  := range .all {
			if manet.IsIPLoopback() {
				.filtered = append(.filtered, )
			}
		}
	}
}

// removeNotInSource removes items from addrs that are not present in source.
// Modifies the addrs slice in place
// addrs and source must be sorted using multiaddr.Compare.
func removeNotInSource(,  []ma.Multiaddr) []ma.Multiaddr {
	 := 0
	// mark entries not in source as nil
	for ,  := range  {
		// move right as long as a > source[j]
		for  < len() && .Compare([]) > 0 {
			++
		}
		// a is not in source if we've reached the end, or a is lesser
		if  == len() || .Compare([]) < 0 {
			[] = nil
		}
		// a is in source, nothing to do
	}
	// j is the current element, i is the lowest index nil element
	 := 0
	for  := range len() {
		if [] != nil {
			[], [] = [], []
			++
		}
	}
	return [:]
}