package connect

import (
	"fmt"
	"net"
	"time"

	"github.com/pion/stun"
	"go.uber.org/zap"
)

// STUNClient STUN 客户端 - 用于 NAT 探测和候选地址采集
type STUNClient struct {
	servers []string
	logger  *zap.Logger
	timeout time.Duration
}

// NewSTUNClient 创建 STUN 客户端
func NewSTUNClient(servers []string, logger *zap.Logger) *STUNClient {
	return &STUNClient{
		servers: servers,
		logger:  logger,
		timeout: 5 * time.Second,
	}
}

// DiscoverAddress 发现外部地址（通过单个 STUN 服务器）
func (c *STUNClient) DiscoverAddress(server string) (*net.UDPAddr, error) {
	host, port, err := net.SplitHostPort(server)
	if err != nil {
		return nil, fmt.Errorf("STUN 服务器地址格式错误：%w", err)
	}

	udpAddr, err := net.ResolveUDPAddr("udp4", net.JoinHostPort(host, port))
	if err != nil {
		return nil, fmt.Errorf("解析 UDP 地址失败：%w", err)
	}

	conn, err := net.DialUDP("udp4", nil, udpAddr)
	if err != nil {
		return nil, fmt.Errorf("连接 STUN 服务器失败：%w", err)
	}
	defer conn.Close()

	conn.SetDeadline(time.Now().Add(c.timeout))

	// 构建 STUN Binding Request
	msg, err := stun.Build(stun.BindingRequest, stun.TransactionID)
	if err != nil {
		return nil, fmt.Errorf("构建 STUN 请求失败：%w", err)
	}

	// 发送请求
	if _, err := conn.Write(msg.Raw); err != nil {
		return nil, fmt.Errorf("发送 STUN 请求失败：%w", err)
	}

	// 读取响应
	buf := make([]byte, 1024)
	n, err := conn.Read(buf)
	if err != nil {
		return nil, fmt.Errorf("读取 STUN 响应失败：%w", err)
	}

	// 解析响应
	res := &stun.Message{Raw: buf[:n]}
	if err := res.Decode(); err != nil {
		return nil, fmt.Errorf("解码 STUN 响应失败：%w", err)
	}

	// 提取 XOR-MAPPED-ADDRESS
	var xorAddr stun.XORMappedAddress
	if err := xorAddr.GetFrom(res); err != nil {
		return nil, fmt.Errorf("提取外部地址失败：%w", err)
	}

	c.logger.Debug("STUN 查询成功",
		zap.String("server", server),
		zap.String("external_addr", xorAddr.String()))

	return &net.UDPAddr{
		IP:   xorAddr.IP,
		Port: xorAddr.Port,
	}, nil
}

// CollectCandidates 收集候选地址（通过多个 STUN 服务器）
func (c *STUNClient) CollectCandidates() []string {
	var candidates []string

	for _, server := range c.servers {
		addr, err := c.DiscoverAddress(server)
		if err != nil {
			c.logger.Debug("STUN 服务器查询失败",
				zap.String("server", server),
				zap.Error(err))
			continue
		}

		candidates = append(candidates, addr.String())
		c.logger.Debug("收集到候选地址",
			zap.String("server", server),
			zap.String("candidate", addr.String()))
	}

	return candidates
}

// GetExternalIP 获取外部 IP（兼容旧 API）
func (c *STUNClient) GetExternalIP() (string, error) {
	for _, server := range c.servers {
		addr, err := c.DiscoverAddress(server)
		if err == nil {
			return addr.IP.String(), nil
		}
	}
	return "", fmt.Errorf("所有 STUN 服务器均查询失败")
}
