package sctp

import (
	"net"
	"time"

	"github.com/pkg/errors"
)

type udpConn struct {
	peerAddr *net.UDPAddr
	conn     *net.UDPConn
}

func newUDPConn(conn *net.UDPConn, addr *Addr) (*udpConn, error) {
	peerAddr, err := addr.UDPAddr()
	if err != nil {
		return nil, err
	}
	return &udpConn{
		peerAddr: peerAddr,
		conn:     conn,
	}, nil
}

func (c *udpConn) Read(p []byte) (int, error) {
	n, addr, err := c.conn.ReadFromUDP(p)
	if err != nil {
		return 0, errors.Wrapf(err, "udp read error")
	}
	if addr.String() != c.peerAddr.String() {
		return 0, errors.Errorf("received data from an unexpected address")
	}
	return n, nil
}

func (c *udpConn) Write(p []byte) (int, error) {
	n, err := c.conn.WriteToUDP(p, c.peerAddr)
	if err != nil {
		return n, errors.Wrapf(err, "udp write error")
	}
	return n, nil
}

func (c *udpConn) Close() error {
	return c.conn.Close()
}

func (c *udpConn) LocalAddr() net.Addr {
	if c.conn != nil {
		return c.conn.LocalAddr()
	}
	return nil
}

func (c *udpConn) RemoteAddr() net.Addr {
	return c.peerAddr
}

func (c *udpConn) SetDeadline(t time.Time) error {
	return nil
}

func (c *udpConn) SetReadDeadline(t time.Time) error {
	return nil
}

func (c *udpConn) SetWriteDeadline(t time.Time) error {
	return nil
}

var privateCIDRs = []string{
	"10.0.0.0/8",
	"172.16.0.0/12",
	"192.168.0.0/16",
}

// IsPrivateIP returns true if IP is in private range.
func IsPrivateIP(ips string) bool {
	ip := net.ParseIP(ips)
	for _, cidr := range privateCIDRs {
		_, nt, err := net.ParseCIDR(cidr)
		if err != nil {
			panic(err)
		}
		if nt.Contains(ip) {
			return true
		}
	}
	return false
}

func externalIP() (string, error) {
	ifaces, err := net.Interfaces()
	if err != nil {
		return "", err
	}
	for _, iface := range ifaces {
		if iface.Flags&net.FlagUp == 0 {
			continue // interface down
		}
		if iface.Flags&net.FlagLoopback != 0 {
			continue // loopback interface
		}
		addrs, err := iface.Addrs()
		if err != nil {
			return "", err
		}
		for _, addr := range addrs {
			var ip net.IP
			switch v := addr.(type) {
			case *net.IPNet:
				ip = v.IP
			case *net.IPAddr:
				ip = v.IP
			}
			if ip == nil || ip.IsLoopback() {
				continue
			}
			ip = ip.To4()
			if ip == nil {
				// TODO: Support IPv6?
				continue // not an ipv4 address
			}
			return ip.String(), nil
		}
	}
	return "", errors.New("no network connection found")
}
