package types

import (
	"net"
	"testing"

	_ "github.com/docker/libnetwork/testutils"
	"gotest.tools/v3/assert"
	is "gotest.tools/v3/assert/cmp"
)

func TestTransportPortConv(t *testing.T) {
	sform := "tcp/23"
	tp := &TransportPort{Proto: TCP, Port: uint16(23)}

	if sform != tp.String() {
		t.Fatalf("String() method failed")
	}

	rc := new(TransportPort)
	if err := rc.FromString(sform); err != nil {
		t.Fatal(err)
	}
	if !tp.Equal(rc) {
		t.Fatalf("FromString() method failed")
	}
}

func TestTransportPortBindingConv(t *testing.T) {
	input := []struct {
		sform      string
		pb         PortBinding
		shouldFail bool
	}{
		{ // IPv4 -> IPv4
			sform: "tcp/172.28.30.23:80/112.0.43.56:8001",
			pb: PortBinding{
				Proto:    TCP,
				IP:       net.IPv4(172, 28, 30, 23),
				Port:     uint16(80),
				HostIP:   net.IPv4(112, 0, 43, 56),
				HostPort: uint16(8001),
			},
		},
		{ // IPv6 -> IPv4
			sform: "tcp/[2001:db8::1]:80/112.0.43.56:8001",
			pb: PortBinding{
				Proto:    TCP,
				IP:       net.IP{0x20, 0x01, 0x0d, 0xb8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1},
				Port:     uint16(80),
				HostIP:   net.IPv4(112, 0, 43, 56),
				HostPort: uint16(8001),
			},
		},
		{ // IPv4inIPv6 -> IPv4
			sform: "tcp/[::ffff:172.28.30.23]:80/112.0.43.56:8001",
			pb: PortBinding{
				Proto:    TCP,
				IP:       net.IPv4(172, 28, 30, 23),
				Port:     uint16(80),
				HostIP:   net.IPv4(112, 0, 43, 56),
				HostPort: uint16(8001),
			},
		},
		{ // IPv4 -> IPv4 zoned
			sform:      "tcp/172.28.30.23:80/169.254.0.23%eth0:8001",
			shouldFail: true,
		},
		{ // IPv4 -> IPv6 zoned
			sform:      "tcp/172.28.30.23:80/[fe80::1ff:fe23:4567:890a%eth0]:8001",
			shouldFail: true,
		},
	}

	for _, in := range input {
		rc := new(PortBinding)
		err := rc.FromString(in.sform)
		if in.shouldFail {
			assert.Assert(t, is.ErrorContains(err, ""), "Unexpected success parsing %s", in.sform)
		} else {
			assert.NilError(t, err)
			assert.Assert(t, is.DeepEqual(in.pb, *rc), "input %s: expected %#v, got %#v", in.sform, in.pb, rc)
		}
	}
}

func TestErrorConstructors(t *testing.T) {
	var err error

	err = BadRequestErrorf("Io ho %d uccello", 1)
	if err.Error() != "Io ho 1 uccello" {
		t.Fatal(err)
	}
	if _, ok := err.(BadRequestError); !ok {
		t.Fatal(err)
	}
	if _, ok := err.(MaskableError); ok {
		t.Fatal(err)
	}

	err = RetryErrorf("Incy wincy %s went up the spout again", "spider")
	if err.Error() != "Incy wincy spider went up the spout again" {
		t.Fatal(err)
	}
	if _, ok := err.(RetryError); !ok {
		t.Fatal(err)
	}
	if _, ok := err.(MaskableError); ok {
		t.Fatal(err)
	}

	err = NotFoundErrorf("Can't find the %s", "keys")
	if err.Error() != "Can't find the keys" {
		t.Fatal(err)
	}
	if _, ok := err.(NotFoundError); !ok {
		t.Fatal(err)
	}
	if _, ok := err.(MaskableError); ok {
		t.Fatal(err)
	}

	err = ForbiddenErrorf("Can't open door %d", 2)
	if err.Error() != "Can't open door 2" {
		t.Fatal(err)
	}
	if _, ok := err.(ForbiddenError); !ok {
		t.Fatal(err)
	}
	if _, ok := err.(MaskableError); ok {
		t.Fatal(err)
	}

	err = NotImplementedErrorf("Functionality %s is not implemented", "x")
	if err.Error() != "Functionality x is not implemented" {
		t.Fatal(err)
	}
	if _, ok := err.(NotImplementedError); !ok {
		t.Fatal(err)
	}
	if _, ok := err.(MaskableError); ok {
		t.Fatal(err)
	}

	err = TimeoutErrorf("Process %s timed out", "abc")
	if err.Error() != "Process abc timed out" {
		t.Fatal(err)
	}
	if _, ok := err.(TimeoutError); !ok {
		t.Fatal(err)
	}
	if _, ok := err.(MaskableError); ok {
		t.Fatal(err)
	}

	err = NoServiceErrorf("Driver %s is not available", "mh")
	if err.Error() != "Driver mh is not available" {
		t.Fatal(err)
	}
	if _, ok := err.(NoServiceError); !ok {
		t.Fatal(err)
	}
	if _, ok := err.(MaskableError); ok {
		t.Fatal(err)
	}

	err = InternalErrorf("Not sure what happened")
	if err.Error() != "Not sure what happened" {
		t.Fatal(err)
	}
	if _, ok := err.(InternalError); !ok {
		t.Fatal(err)
	}
	if _, ok := err.(MaskableError); ok {
		t.Fatal(err)
	}

	err = InternalMaskableErrorf("Minor issue, it can be ignored")
	if err.Error() != "Minor issue, it can be ignored" {
		t.Fatal(err)
	}
	if _, ok := err.(InternalError); !ok {
		t.Fatal(err)
	}
	if _, ok := err.(MaskableError); !ok {
		t.Fatal(err)
	}
}

func TestCompareIPMask(t *testing.T) {
	input := []struct {
		ip    net.IP
		mask  net.IPMask
		is    int
		ms    int
		isErr bool
	}{
		{ // ip in v4Inv6 representation, mask in v4 representation
			ip:   net.IPv4(172, 28, 30, 1),
			mask: []byte{0xff, 0xff, 0xff, 0},
			is:   12,
			ms:   0,
		},
		{ // ip and mask in v4Inv6 representation
			ip:   net.IPv4(172, 28, 30, 2),
			mask: []byte{0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0},
			is:   12,
			ms:   12,
		},
		{ // ip in v4 representation, mask in v4Inv6 representation
			ip:   net.IPv4(172, 28, 30, 3)[12:],
			mask: []byte{0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0},
			is:   0,
			ms:   12,
		},
		{ // ip and mask in v4 representation
			ip:   net.IPv4(172, 28, 30, 4)[12:],
			mask: []byte{0xff, 0xff, 0xff, 0},
			is:   0,
			ms:   0,
		},
		{ // ip and mask as v6
			ip:   net.ParseIP("2001:DB8:2002:2001:FFFF:ABCD:EEAB:00CD"),
			mask: []byte{0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0, 0, 0},
			is:   0,
			ms:   0,
		},
		{
			ip:    net.ParseIP("2001:DB8:2002:2001:FFFF:ABCD:EEAB:00CD"),
			mask:  []byte{0xff, 0xff, 0xff, 0},
			isErr: true,
		},
		{
			ip:    net.ParseIP("173.32.4.5"),
			mask:  []byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0xff, 0xff, 0xff, 0},
			isErr: true,
		},
		{
			ip:    net.ParseIP("173.32.4.5"),
			mask:  []byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0xff, 0xff, 0xff, 0xff, 0xff, 0},
			isErr: true,
		},
	}

	for ind, i := range input {
		is, ms, err := compareIPMask(i.ip, i.mask)
		if i.isErr {
			if err == nil {
				t.Fatalf("Incorrect error condition for element %d. is: %d, ms: %d, err: %v", ind, is, ms, err)
			}
		} else {
			if i.is != is || i.ms != ms {
				t.Fatalf("expected is: %d, ms: %d. Got is: %d, ms: %d for element %d", i.is, i.ms, is, ms, ind)
			}
		}
	}
}

func TestUtilGetHostPartIP(t *testing.T) {
	input := []struct {
		ip   net.IP
		mask net.IPMask
		host net.IP
		err  error
	}{
		{ // ip in v4Inv6 representation, mask in v4 representation
			ip:   net.IPv4(172, 28, 30, 1),
			mask: []byte{0xff, 0xff, 0xff, 0},
			host: net.IPv4(0, 0, 0, 1),
		},
		{ // ip and mask in v4Inv6 representation
			ip:   net.IPv4(172, 28, 30, 2),
			mask: []byte{0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0},
			host: net.IPv4(0, 0, 0, 2),
		},
		{ // ip in v4 representation, mask in v4Inv6 representation
			ip:   net.IPv4(172, 28, 30, 3)[12:],
			mask: []byte{0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0},
			host: net.IPv4(0, 0, 0, 3)[12:],
		},
		{ // ip and mask in v4 representation
			ip:   net.IPv4(172, 28, 30, 4)[12:],
			mask: []byte{0xff, 0xff, 0xff, 0},
			host: net.IPv4(0, 0, 0, 4)[12:],
		},
		{ // ip and mask as v6
			ip:   net.ParseIP("2001:DB8:2002:2001:FFFF:ABCD:EEAB:00CD"),
			mask: []byte{0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0, 0, 0},
			host: net.ParseIP("0::AB:00CD"),
		},
	}

	for _, i := range input {
		h, err := GetHostPartIP(i.ip, i.mask)
		if err != nil {
			t.Fatal(err)
		}
		if !i.host.Equal(h) {
			t.Fatalf("Failed to return expected host ip. Expected: %s. Got: %s", i.host, h)
		}
	}

	// ip as v6 and mask as v4 are not compatible
	if _, err := GetHostPartIP(net.ParseIP("2001:DB8:2002:2001:FFFF:ABCD:EEAB:00CD"), []byte{0xff, 0xff, 0xff, 0}); err == nil {
		t.Fatalf("Unexpected success")
	}
	// ip as v4 and non conventional mask
	if _, err := GetHostPartIP(net.ParseIP("173.32.4.5"), []byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0xff, 0xff, 0xff, 0}); err == nil {
		t.Fatalf("Unexpected success")
	}
	// ip as v4 and non conventional mask
	if _, err := GetHostPartIP(net.ParseIP("173.32.4.5"), []byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0xff, 0xff, 0xff, 0xff, 0xff, 0}); err == nil {
		t.Fatalf("Unexpected success")
	}
}

func TestUtilGetBroadcastIP(t *testing.T) {
	input := []struct {
		ip    net.IP
		mask  net.IPMask
		bcast net.IP
		err   error
	}{
		// ip in v4Inv6 representation, mask in v4 representation
		{
			ip:    net.IPv4(172, 28, 30, 1),
			mask:  []byte{0xff, 0xff, 0xff, 0},
			bcast: net.IPv4(172, 28, 30, 255),
		},
		{
			ip:    net.IPv4(10, 28, 30, 1),
			mask:  []byte{0xff, 0, 0, 0},
			bcast: net.IPv4(10, 255, 255, 255),
		},
		// ip and mask in v4Inv6 representation
		{
			ip:    net.IPv4(172, 28, 30, 2),
			mask:  []byte{0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0},
			bcast: net.IPv4(172, 28, 30, 255),
		},
		{
			ip:    net.IPv4(172, 28, 30, 2),
			mask:  []byte{0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0, 0},
			bcast: net.IPv4(172, 28, 255, 255),
		},
		// ip in v4 representation, mask in v4Inv6 representation
		{
			ip:    net.IPv4(172, 28, 30, 3)[12:],
			mask:  []byte{0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0},
			bcast: net.IPv4(172, 28, 30, 255)[12:],
		},
		{
			ip:    net.IPv4(172, 28, 30, 3)[12:],
			mask:  []byte{0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0, 0, 0},
			bcast: net.IPv4(172, 255, 255, 255)[12:],
		},
		// ip and mask in v4 representation
		{
			ip:    net.IPv4(172, 28, 30, 4)[12:],
			mask:  []byte{0xff, 0xff, 0xff, 0},
			bcast: net.IPv4(172, 28, 30, 255)[12:],
		},
		{
			ip:    net.IPv4(172, 28, 30, 4)[12:],
			mask:  []byte{0xff, 0xff, 0, 0},
			bcast: net.IPv4(172, 28, 255, 255)[12:],
		},
		{ // ip and mask as v6
			ip:    net.ParseIP("2001:DB8:2002:2001:FFFF:ABCD:EEAB:00CD"),
			mask:  []byte{0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0, 0, 0},
			bcast: net.ParseIP("2001:DB8:2002:2001:FFFF:ABCD:EEFF:FFFF"),
		},
	}

	for _, i := range input {
		h, err := GetBroadcastIP(i.ip, i.mask)
		if err != nil {
			t.Fatal(err)
		}
		if !i.bcast.Equal(h) {
			t.Fatalf("Failed to return expected host ip. Expected: %s. Got: %s", i.bcast, h)
		}
	}

	// ip as v6 and mask as v4 are not compatible
	if _, err := GetBroadcastIP(net.ParseIP("2001:DB8:2002:2001:FFFF:ABCD:EEAB:00CD"), []byte{0xff, 0xff, 0xff, 0}); err == nil {
		t.Fatalf("Unexpected success")
	}
	// ip as v4 and non conventional mask
	if _, err := GetBroadcastIP(net.ParseIP("173.32.4.5"), []byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0xff, 0xff, 0xff, 0}); err == nil {
		t.Fatalf("Unexpected success")
	}
	// ip as v4 and non conventional mask
	if _, err := GetBroadcastIP(net.ParseIP("173.32.4.5"), []byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0xff, 0xff, 0xff, 0xff, 0xff, 0}); err == nil {
		t.Fatalf("Unexpected success")
	}
}

func TestParseCIDR(t *testing.T) {
	input := []struct {
		cidr string
		ipnw *net.IPNet
	}{
		{"192.168.22.44/16", &net.IPNet{IP: net.IP{192, 168, 22, 44}, Mask: net.IPMask{255, 255, 0, 0}}},
		{"10.10.2.0/24", &net.IPNet{IP: net.IP{10, 10, 2, 0}, Mask: net.IPMask{255, 255, 255, 0}}},
		{"10.0.0.100/17", &net.IPNet{IP: net.IP{10, 0, 0, 100}, Mask: net.IPMask{255, 255, 128, 0}}},
	}

	for _, i := range input {
		nw, err := ParseCIDR(i.cidr)
		if err != nil {
			t.Fatal(err)
		}
		if !CompareIPNet(nw, i.ipnw) {
			t.Fatalf("network differ. Expected %v. Got: %v", i.ipnw, nw)
		}
	}
}
