package server

import (
	"log"
	"net/netip"

	"github.com/openp2p-cn/inp2p/pkg/protocol"
)

func (s *Server) GetSDWAN() protocol.SDWANConfig {
	return s.sdwan.get()
}

func (s *Server) GetSDWANTenant(tenantID int64) protocol.SDWANConfig {
	return s.sdwan.getTenant(tenantID)
}

func (s *Server) SetSDWAN(cfg protocol.SDWANConfig) error {
	if err := s.sdwan.save(cfg); err != nil {
		return err
	}
	s.broadcastSDWAN(s.sdwan.get())
	return nil
}

func (s *Server) SetSDWANTenant(tenantID int64, cfg protocol.SDWANConfig) error {
	if err := s.sdwan.saveTenant(tenantID, cfg); err != nil {
		return err
	}
	s.broadcastSDWANTenant(tenantID, s.sdwan.getTenant(tenantID))
	return nil
}

func (s *Server) broadcastSDWAN(cfg protocol.SDWANConfig) {
	if !cfg.Enabled || cfg.GatewayCIDR == "" {
		return
	}
	s.mu.RLock()
	defer s.mu.RUnlock()
	for _, n := range s.nodes {
		if !n.IsOnline() {
			continue
		}
		_ = n.Conn.Write(protocol.MsgPush, protocol.SubPushSDWANConfig, cfg)
	}
}

func (s *Server) broadcastSDWANTenant(tenantID int64, cfg protocol.SDWANConfig) {
	if !cfg.Enabled || cfg.GatewayCIDR == "" {
		return
	}
	s.mu.RLock()
	defer s.mu.RUnlock()
	for _, n := range s.nodes {
		if !n.IsOnline() || n.TenantID != tenantID {
			continue
		}
		_ = n.Conn.Write(protocol.MsgPush, protocol.SubPushSDWANConfig, cfg)
	}
}

func (s *Server) pushSDWANPeer(to *NodeInfo, peer protocol.SDWANPeer) {
	if to == nil || !to.IsOnline() {
		return
	}
	_ = to.Conn.Write(protocol.MsgPush, protocol.SubPushSDWANPeer, peer)
}

func (s *Server) pushSDWANDel(to *NodeInfo, peer protocol.SDWANPeer) {
	if to == nil || !to.IsOnline() {
		return
	}
	_ = to.Conn.Write(protocol.MsgPush, protocol.SubPushSDWANDel, peer)
}

func (s *Server) announceSDWANNodeOnline(nodeName string) {
	// pick tenant config by node
	s.mu.RLock()
	newNode := s.nodes[nodeName]
	s.mu.RUnlock()
	if newNode == nil {
		return
	}
	cfg := s.sdwan.getTenant(newNode.TenantID)
	if cfg.GatewayCIDR == "" {
		return
	}
	selfIP := ""
	for _, n := range cfg.Nodes {
		if n.Node == nodeName {
			selfIP = n.IP
			break
		}
	}
	if selfIP == "" {
		return
	}

	s.mu.RLock()
	newNode = s.nodes[nodeName]
	if newNode == nil || !newNode.IsOnline() {
		s.mu.RUnlock()
		return
	}
	for _, n := range cfg.Nodes {
		if n.Node == nodeName {
			continue
		}
		other := s.nodes[n.Node]
		if other == nil || !other.IsOnline() || other.TenantID != newNode.TenantID {
			continue
		}
		// existing -> new
		s.pushSDWANPeer(newNode, protocol.SDWANPeer{Node: n.Node, IP: n.IP, Online: true})
		// new -> existing
		s.pushSDWANPeer(other, protocol.SDWANPeer{Node: nodeName, IP: selfIP, Online: true})
	}
	s.mu.RUnlock()
}

func (s *Server) announceSDWANNodeOffline(nodeName string) {
	s.mu.RLock()
	old := s.nodes[nodeName]
	s.mu.RUnlock()
	if old == nil {
		return
	}
	cfg := s.sdwan.getTenant(old.TenantID)
	if cfg.GatewayCIDR == "" {
		return
	}
	selfIP := ""
	for _, n := range cfg.Nodes {
		if n.Node == nodeName {
			selfIP = n.IP
			break
		}
	}
	s.mu.RLock()
	defer s.mu.RUnlock()
	for _, n := range s.nodes {
		if n.Name == nodeName || !n.IsOnline() || n.TenantID != old.TenantID {
			continue
		}
		s.pushSDWANDel(n, protocol.SDWANPeer{Node: nodeName, IP: selfIP, Online: false})
	}
}

func (s *Server) RouteSDWANPacket(from *NodeInfo, pkt protocol.SDWANPacket) {
	log.Printf("[sdwan] route: %s -> %s len=%d", from.Name, pkt.DstIP, len(pkt.Payload))
	if from == nil {
		return
	}
	// Use global config for untrusted nodes (TenantID=0), otherwise use tenant config
	var cfg protocol.SDWANConfig
	if from.TenantID == 0 {
		cfg = s.sdwan.get()
	} else {
		cfg = s.sdwan.getTenant(from.TenantID)
	}
	if cfg.GatewayCIDR == "" || pkt.DstIP == "" || len(pkt.Payload) == 0 {
		return
	}

	dst, err := netip.ParseAddr(pkt.DstIP)
	if err != nil {
		return
	}
	toNode := ""
	for _, n := range cfg.Nodes {
		if n.IP == pkt.DstIP {
			candidate := s.GetNodeForUser(n.Node, from.Token)
			if candidate != nil && candidate.TenantID == from.TenantID {
				toNode = n.Node
				break
			}
		}
		if p, err := netip.ParseAddr(n.IP); err == nil && p == dst {
			candidate := s.GetNodeForUser(n.Node, from.Token)
			if candidate != nil && candidate.TenantID == from.TenantID {
				toNode = n.Node
				break
			}
		}
	}
	if toNode == "" || toNode == from.Name {
		return
	}

	s.mu.RLock()
	to := s.nodes[toNode]
	if to != nil && to.TenantID != from.TenantID {
		to = nil
	}
	s.mu.RUnlock()
	if to == nil || !to.IsOnline() {
		return
	}

	pkt.FromNode = from.Name
	pkt.ToNode = toNode
	frame := protocol.EncodeRaw(protocol.MsgTunnel, protocol.SubTunnelSDWANRaw, pkt.Payload)
	_ = to.Conn.WriteRaw(frame)
}
