package tendrils

import (
	"context"
	"log"
	"net"
	"time"

	"github.com/google/gopacket"
	"github.com/google/gopacket/layers"
	"github.com/google/gopacket/pcap"
)

func (t *Tendrils) listenLLDP(ctx context.Context, iface net.Interface) {
	handle, err := pcap.OpenLive(iface.Name, 65536, true, 5*time.Second)
	if err != nil {
		log.Printf("[ERROR] failed to open interface %s: %v", iface.Name, err)
		return
	}
	defer handle.Close()

	if err := handle.SetBPFFilter("ether proto 0x88cc"); err != nil {
		log.Printf("[ERROR] failed to set BPF filter on %s: %v", iface.Name, err)
		return
	}

	packetSource := gopacket.NewPacketSource(handle, handle.LinkType())
	packets := packetSource.Packets()

	for {
		select {
		case <-ctx.Done():
			return
		case packet, ok := <-packets:
			if !ok {
				return
			}
			t.handleLLDPPacket(iface.Name, packet)
		}
	}
}

func (t *Tendrils) handleLLDPPacket(ifaceName string, packet gopacket.Packet) {
	lldpLayer := packet.Layer(layers.LayerTypeLinkLayerDiscovery)
	if lldpLayer == nil {
		return
	}

	lldp, ok := lldpLayer.(*layers.LinkLayerDiscovery)
	if !ok {
		return
	}

	if len(lldp.ChassisID.ID) == 6 {
		mac := net.HardwareAddr(lldp.ChassisID.ID)
		if !isBroadcastOrZero(mac) {
			childPort := string(lldp.PortID.ID)

			var systemName string
			for _, opt := range lldp.Values {
				if opt.Type == layers.LLDPTLVSysName {
					systemName = string(opt.Value)
					break
				}
			}

			if t.DebugLLDP {
				log.Printf("[lldp] %s: mac=%s port=%s name=%s", ifaceName, mac, childPort, systemName)
			}

			t.nodes.Update(nil, mac, nil, childPort, systemName, "lldp")
		}
	}
}

func isBroadcastOrZero(mac net.HardwareAddr) bool {
	if len(mac) != 6 {
		return true
	}

	allZero := true
	allFF := true

	for _, b := range mac {
		if b != 0x00 {
			allZero = false
		}
		if b != 0xff {
			allFF = false
		}
	}

	if allZero || allFF {
		return true
	}

	if mac[0] == 0x01 && mac[1] == 0x00 && mac[2] == 0x5e {
		return true
	}

	if mac[0] == 0x33 && mac[1] == 0x33 {
		return true
	}

	return false
}
