//go:build p2p

package client

import (
	"context"
	"encoding/json"
	"log"

	"github.com/libp2p/go-libp2p"
	"github.com/libp2p/go-libp2p/core/host"
	"github.com/libp2p/go-libp2p/core/network"
	"github.com/libp2p/go-libp2p/core/peer"
	"github.com/libp2p/go-libp2p/p2p/discovery/mdns"
)

const MeshProtocol = "/aetherforge/mesh/1.0.0"
const DiscoveryTag = "aetherforge-mesh-discovery"

// MeshNode represents a libp2p peer on the local network.
type MeshNode struct {
	host   host.Host
	client *AgentClient
}

// NewMeshNode creates a new P2P fallback node.
func NewMeshNode(c *AgentClient) *MeshNode {
	return &MeshNode{client: c}
}

// Start initializes the libp2p host and mDNS discovery.
func (m *MeshNode) Start() error {
	// Bind to any available local port automatically
	h, err := libp2p.New(libp2p.ListenAddrStrings("/ip4/0.0.0.0/tcp/0"))
	if err != nil {
		return err
	}
	m.host = h

	// Register the protocol handler for incoming mesh streams
	m.host.SetStreamHandler(MeshProtocol, m.handleStream)

	// Start mDNS discovery to find other agents on the LAN
	ser := mdns.NewMdnsService(m.host, DiscoveryTag, m)
	if err := ser.Start(); err != nil {
		return err
	}

	log.Printf("[Mesh] P2P Node started. PeerID: %s", m.host.ID().String())
	return nil
}

// HandlePeerFound is a callback for mDNS discovery.
func (m *MeshNode) HandlePeerFound(pi peer.AddrInfo) {
	if pi.ID == m.host.ID() {
		return
	}
	log.Printf("[Mesh] Discovered peer on LAN: %s", pi.ID.String())
	if err := m.host.Connect(context.Background(), pi); err != nil {
		log.Printf("[Mesh] Failed to connect to peer %s: %v", pi.ID, err)
	}
}

// handleStream processes incoming messages from orphaned peers.
func (m *MeshNode) handleStream(s network.Stream) {
	defer s.Close()
	var msg Message
	if err := json.NewDecoder(s).Decode(&msg); err != nil {
		return
	}

	// If this node is actively connected to the Hub, act as a Relay.
	// We take the incoming share payload from the orphaned peer and pass it to our active connection!
	if m.client.conn != nil {
		log.Printf("[Mesh] Relaying %s message from orphaned peer to Hub", msg.Type)
		_ = m.client.write(msg)
	}
}

// BroadcastToMesh sends a message to all connected P2P peers.
func (m *MeshNode) BroadcastToMesh(msg Message) {
	for _, p := range m.host.Network().Peers() {
		s, err := m.host.NewStream(context.Background(), p, MeshProtocol)
		if err != nil {
			continue
		}
		_ = json.NewEncoder(s).Encode(msg)
		s.Close()
	}
}

// PeerCount returns the number of connected mesh peers.
func (m *MeshNode) PeerCount() int {
	if m.host == nil {
		return 0
	}
	return len(m.host.Network().Peers())
}
