package connect

import (
	"context"
	"fmt"
	"net"
	"sync"
	"time"

	"github.com/pion/turn/v2"
	"go.uber.org/zap"
)

// TURNProtocol TURN 协议类型
type TURNProtocol string

const (
	TURNProtocolUDP TURNProtocol = "udp"
	TURNProtocolTCP TURNProtocol = "tcp"
	TURNProtocolTLS TURNProtocol = "tls"
)

// TURNFactory TURN 工厂（Layer 4-6: TURN-UDP/TCP/TLS）
// 自包含实现：TURN 协议协商 + 建连
type TURNFactory struct {
	protocol TURNProtocol
	servers  []string
	username string
	password string
	logger   *zap.Logger
}

// NewTURNFactory 创建 TURN 工厂
func NewTURNFactory(protocol TURNProtocol, servers []string, username, password string, logger *zap.Logger) *TURNFactory {
	return &TURNFactory{
		protocol: protocol,
		servers:  servers,
		username: username,
		password: password,
		logger:   logger,
	}
}

// Layer 返回传输层类型
func (f *TURNFactory) Layer() Layer {
	switch f.protocol {
	case TURNProtocolUDP:
		return LayerTURNUDP
	case TURNProtocolTCP:
		return LayerTURNTCP
	case TURNProtocolTLS:
		return LayerTURNTLS
	default:
		return LayerTURNUDP
	}
}

// Name 返回名称
func (f *TURNFactory) Name() string {
	switch f.protocol {
	case TURNProtocolUDP:
		return "TURN-UDP"
	case TURNProtocolTCP:
		return "TURN-TCP"
	case TURNProtocolTLS:
		return "TURN-TLS"
	default:
		return "TURN-UDP"
	}
}

// Dial 建立 TURN 中继连接
func (f *TURNFactory) Dial(ctx context.Context, config *DialConfig) (net.Conn, error) {
	f.logger.Info("开始建立 TURN 中继连接",
		zap.String("peer_id", config.PeerID),
		zap.String("protocol", string(f.protocol)))

	servers := f.servers
	if len(servers) == 0 {
		servers = config.TURNServers
	}

	if len(servers) == 0 {
		return nil, fmt.Errorf("未配置 TURN 服务器")
	}

	// 解析第一个 TURN 服务器
	server := servers[0]
	host, port := parseServerAddr(server)

	// 根据协议类型建立连接
	var relayConn net.PacketConn
	var err error

	switch f.protocol {
	case TURNProtocolUDP:
		relayConn, err = f.allocateUDP(ctx, host, port, config)
	case TURNProtocolTCP:
		relayConn, err = f.allocateTCP(ctx, host, port, config)
	case TURNProtocolTLS:
		return nil, fmt.Errorf("TURN-TLS 尚未实现")
	default:
		return nil, fmt.Errorf("不支持的 TURN 协议：%s", f.protocol)
	}

	if err != nil {
		return nil, fmt.Errorf("TURN 分配失败：%w", err)
	}

	f.logger.Info("TURN 中继连接建立成功",
		zap.String("peer_id", config.PeerID),
		zap.String("relay_addr", relayConn.LocalAddr().String()))

	// 包装成 net.Conn 返回
	return newTURNConn(relayConn, f.logger), nil
}

// allocateUDP UDP TURN 分配
func (f *TURNFactory) allocateUDP(ctx context.Context, host, port string, config *DialConfig) (net.PacketConn, error) {
	udpAddr, err := net.ResolveUDPAddr("udp", net.JoinHostPort(host, port))
	if err != nil {
		return nil, fmt.Errorf("解析 UDP 地址失败：%w", err)
	}

	conn, err := net.DialUDP("udp", nil, udpAddr)
	if err != nil {
		return nil, fmt.Errorf("创建 UDP 连接失败：%w", err)
	}

	clientConfig := &turn.ClientConfig{
		STUNServerAddr: net.JoinHostPort(host, port),
		TURNServerAddr: net.JoinHostPort(host, port),
		Username:       f.username,
		Password:       f.password,
		Conn:           conn,
	}

	client, err := turn.NewClient(clientConfig)
	if err != nil {
		conn.Close()
		return nil, fmt.Errorf("创建 TURN 客户端失败：%w", err)
	}

	if err := client.Listen(); err != nil {
		client.Close()
		conn.Close()
		return nil, fmt.Errorf("TURN 客户端监听失败：%w", err)
	}

	relayConn, err := client.Allocate()
	if err != nil {
		client.Close()
		conn.Close()
		return nil, fmt.Errorf("分配 TURN 中继失败：%w", err)
	}

	// 创建 Permission（允许特定对端地址使用中继）
	// 这是 TURN 协议的关键步骤，否则无法收发数据
	// 注意：PeerID 在这里应该是对端的公网地址（由信使服务器转发）
	if config != nil && config.PeerID != "" {
		peerAddr, err := net.ResolveUDPAddr("udp", config.PeerID)
		if err == nil {
			if permErr := client.CreatePermission(peerAddr); permErr != nil {
				f.logger.Warn("CreatePermission 失败",
					zap.String("peer_addr", peerAddr.String()),
					zap.Error(permErr))
				// 注意：CreatePermission 失败不影响连接建立，只是警告
			} else {
				f.logger.Debug("CreatePermission 成功",
					zap.String("peer_addr", peerAddr.String()))
			}
		}
	}

	f.logger.Debug("TURN-UDP 分配成功",
		zap.String("relay_addr", relayConn.LocalAddr().String()))

	return relayConn, nil
}

// allocateTCP TCP TURN 分配
func (f *TURNFactory) allocateTCP(ctx context.Context, host, port string, config *DialConfig) (net.PacketConn, error) {
	dialer := &net.Dialer{Timeout: 10 * time.Second}
	conn, err := dialer.DialContext(ctx, "tcp", net.JoinHostPort(host, port))
	if err != nil {
		return nil, fmt.Errorf("TCP 连接失败：%w", err)
	}

	packetConn := newTCPPacketConn(conn, f.logger)

	clientConfig := &turn.ClientConfig{
		STUNServerAddr: net.JoinHostPort(host, port),
		TURNServerAddr: net.JoinHostPort(host, port),
		Username:       f.username,
		Password:       f.password,
		Conn:           packetConn,
	}

	client, err := turn.NewClient(clientConfig)
	if err != nil {
		conn.Close()
		return nil, fmt.Errorf("创建 TURN 客户端失败：%w", err)
	}

	if err := client.Listen(); err != nil {
		client.Close()
		conn.Close()
		return nil, fmt.Errorf("TURN 客户端监听失败：%w", err)
	}

	relayConn, err := client.Allocate()
	if err != nil {
		client.Close()
		conn.Close()
		return nil, fmt.Errorf("分配 TURN 中继失败：%w", err)
	}

	// 创建 Permission（允许特定对端地址使用中继）
	if config != nil && config.PeerID != "" {
		peerAddr, err := net.ResolveTCPAddr("tcp", config.PeerID)
		if err == nil {
			if permErr := client.CreatePermission(peerAddr); permErr != nil {
				f.logger.Warn("CreatePermission 失败",
					zap.String("peer_addr", peerAddr.String()),
					zap.Error(permErr))
			} else {
				f.logger.Debug("CreatePermission 成功",
					zap.String("peer_addr", peerAddr.String()))
			}
		}
	}

	f.logger.Debug("TURN-TCP 分配成功",
		zap.String("relay_addr", relayConn.LocalAddr().String()))

	return relayConn, nil
}

// parseServerAddr 解析服务器地址
func parseServerAddr(server string) (host, port string) {
	h, p, _ := net.SplitHostPort(server)
	if h == "" {
		h = server
		p = "3478" // 默认 TURN 端口
	}
	return h, p
}

// turnConn TURN 连接包装器
type turnConn struct {
	relay      net.PacketConn
	remoteAddr net.Addr // 对端地址
	buffer     []byte
	logger     *zap.Logger
	mu         sync.Mutex
}

// newTURNConn 创建 TURN 连接
func newTURNConn(relay net.PacketConn, logger *zap.Logger) *turnConn {
	return &turnConn{
		relay:  relay,
		buffer: make([]byte, 65535),
		logger: logger,
	}
}

// SetRemoteAddr 设置对端地址（必须在 Write 之前调用）
func (c *turnConn) SetRemoteAddr(addr net.Addr) {
	c.mu.Lock()
	defer c.mu.Unlock()
	c.remoteAddr = addr
}

func (c *turnConn) Read(b []byte) (n int, err error) {
	c.mu.Lock()
	defer c.mu.Unlock()

	n, _, err = c.relay.ReadFrom(b)
	return n, err
}

func (c *turnConn) Write(b []byte) (n int, err error) {
	c.mu.Lock()
	defer c.mu.Unlock()

	if c.remoteAddr == nil {
		return 0, fmt.Errorf("未设置对端地址，请先调用 SetRemoteAddr()")
	}

	n, err = c.relay.WriteTo(b, c.remoteAddr)
	return n, err
}

func (c *turnConn) Close() error {
	return c.relay.Close()
}

func (c *turnConn) LocalAddr() net.Addr {
	return c.relay.LocalAddr()
}

func (c *turnConn) RemoteAddr() net.Addr {
	return nil // TURN 中继没有固定的 RemoteAddr
}

func (c *turnConn) SetDeadline(t time.Time) error {
	return c.relay.SetDeadline(t)
}

func (c *turnConn) SetReadDeadline(t time.Time) error {
	return c.relay.SetReadDeadline(t)
}

func (c *turnConn) SetWriteDeadline(t time.Time) error {
	return c.relay.SetWriteDeadline(t)
}

// tcpPacketConn TCP PacketConn 包装器
type tcpPacketConn struct {
	conn   net.Conn
	logger *zap.Logger
}

func newTCPPacketConn(conn net.Conn, logger *zap.Logger) *tcpPacketConn {
	return &tcpPacketConn{
		conn:   conn,
		logger: logger,
	}
}

func (p *tcpPacketConn) ReadFrom(b []byte) (n int, addr net.Addr, err error) {
	n, err = p.conn.Read(b)
	return n, p.conn.RemoteAddr(), err
}

func (p *tcpPacketConn) WriteTo(b []byte, addr net.Addr) (n int, err error) {
	return p.conn.Write(b)
}

func (p *tcpPacketConn) Close() error {
	return p.conn.Close()
}

func (p *tcpPacketConn) LocalAddr() net.Addr {
	return p.conn.LocalAddr()
}

func (p *tcpPacketConn) SetDeadline(t time.Time) error {
	return p.conn.SetDeadline(t)
}

func (p *tcpPacketConn) SetReadDeadline(t time.Time) error {
	return p.conn.SetReadDeadline(t)
}

func (p *tcpPacketConn) SetWriteDeadline(t time.Time) error {
	return p.conn.SetWriteDeadline(t)
}
