package containers

import (
	"testing"

	"github.com/containers/common/libnetwork/types"
	"github.com/stretchr/testify/assert"
)

func Test_portsToString(t *testing.T) {
	tests := []struct {
		name         string
		ports        []types.PortMapping
		exposedPorts map[uint16][]string
		want         string
	}{
		{
			name: "no ports",
			want: "",
		},
		{
			name:         "no ports empty map/slice",
			ports:        []types.PortMapping{},
			exposedPorts: map[uint16][]string{},
			want:         "",
		},
		{
			name: "single published port",
			ports: []types.PortMapping{
				{
					ContainerPort: 80,
					HostPort:      8080,
					Protocol:      "tcp",
					Range:         1,
				},
			},
			want: "0.0.0.0:8080->80/tcp",
		},
		{
			name: "published port with host ip",
			ports: []types.PortMapping{
				{
					ContainerPort: 80,
					HostPort:      8080,
					HostIP:        "127.0.0.1",
					Protocol:      "tcp",
					Range:         1,
				},
			},
			want: "127.0.0.1:8080->80/tcp",
		},
		{
			name: "published port with same exposed port",
			ports: []types.PortMapping{
				{
					ContainerPort: 80,
					HostPort:      8080,
					Protocol:      "tcp",
					Range:         1,
				},
			},
			exposedPorts: map[uint16][]string{
				80: {"tcp"},
			},
			want: "0.0.0.0:8080->80/tcp",
		},
		{
			name: "published port and exposed port with different protocol",
			ports: []types.PortMapping{
				{
					ContainerPort: 80,
					HostPort:      8080,
					Protocol:      "tcp",
					Range:         1,
				},
			},
			exposedPorts: map[uint16][]string{
				80: {"udp"},
			},
			want: "0.0.0.0:8080->80/tcp, 80/udp",
		},
		{
			name: "published port range",
			ports: []types.PortMapping{
				{
					ContainerPort: 80,
					HostPort:      8080,
					Protocol:      "tcp",
					Range:         3,
				},
			},
			want: "0.0.0.0:8080-8082->80-82/tcp",
		},
		{
			name: "published port range and exposed port in that range",
			ports: []types.PortMapping{
				{
					ContainerPort: 80,
					HostPort:      8080,
					Protocol:      "tcp",
					Range:         3,
				},
			},
			exposedPorts: map[uint16][]string{
				81: {"tcp"},
			},
			want: "0.0.0.0:8080-8082->80-82/tcp",
		},
		{
			name: "two published ports",
			ports: []types.PortMapping{
				{
					ContainerPort: 80,
					HostPort:      8080,
					Protocol:      "tcp",
					Range:         3,
				},
				{
					ContainerPort: 80,
					HostPort:      8080,
					Protocol:      "udp",
					Range:         1,
				},
			},
			want: "0.0.0.0:8080-8082->80-82/tcp, 0.0.0.0:8080->80/udp",
		},
		{
			name: "exposed port",
			exposedPorts: map[uint16][]string{
				80: {"tcp"},
			},
			want: "80/tcp",
		},
		{
			name: "exposed port multiple protocols",
			exposedPorts: map[uint16][]string{
				80: {"tcp", "udp"},
			},
			want: "80/tcp, 80/udp",
		},
		{
			name: "exposed port range",
			exposedPorts: map[uint16][]string{
				80: {"tcp"},
				81: {"tcp"},
				82: {"tcp"},
			},
			want: "80-82/tcp",
		},
		{
			name: "exposed port range with different protocols",
			exposedPorts: map[uint16][]string{
				80: {"tcp", "udp"},
				81: {"tcp", "sctp"},
				82: {"tcp", "udp"},
			},
			want: "81/sctp, 80-82/tcp, 80/udp, 82/udp",
		},
		{
			name: "multiple exposed port ranges",
			exposedPorts: map[uint16][]string{
				80: {"tcp"},
				81: {"tcp"},
				82: {"tcp"},
				// 83 missing to split the range
				84: {"tcp"},
				85: {"tcp"},
				86: {"tcp"},
			},
			want: "80-82/tcp, 84-86/tcp",
		},
		{
			name: "published port range partially overlaps with exposed port range",
			ports: []types.PortMapping{
				{
					ContainerPort: 80,
					HostPort:      8080,
					Protocol:      "tcp",
					Range:         3,
				},
			},
			exposedPorts: map[uint16][]string{
				82: {"tcp"},
				83: {"tcp"},
				84: {"tcp"},
			},
			want: "0.0.0.0:8080-8082->80-82/tcp, 83-84/tcp",
		},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			got := portsToString(tt.ports, tt.exposedPorts)
			assert.Equal(t, tt.want, got)
		})
	}
}
