package kas

import (
	"net"
	"testing"

	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"
	"gitlab.com/gitlab-org/cluster-integration/gitlab-agent/v18/internal/tool/testing/testlogger"
)

func TestConstructOwnURL(t *testing.T) {
	tests := []struct {
		name                                                                           string
		ownURL, ownCIDRs, ownScheme, ownTLSHost, ownPort, listenNetwork, listenAddress string
		ips                                                                            []net.IP
		isTLS                                                                          bool

		expectedErr string
		expectedURL string
	}{
		{
			name:        "URL specified explicitly",
			ownURL:      "grpc://127.0.0.1:900",
			expectedURL: "grpc:127.0.0.1|900",
		},
		{
			name:        "CIDR specified, no port",
			ownCIDRs:    "10.0.0.0/8",
			ips:         []net.IP{{10, 0, 0, 1}},
			expectedErr: "unsupported network type specified in the listener config: ",
		},
		{
			name:          "CIDR specified, no port, listen addr",
			ownCIDRs:      "10.0.0.0/8",
			listenNetwork: "tcp",
			listenAddress: ":123",
			ips:           []net.IP{{10, 0, 0, 1}},
			expectedURL:   "grpc:10.0.0.1|123",
		},
		{
			name:        "CIDR, port specified",
			ownCIDRs:    "10.0.0.0/8",
			ownPort:     "911",
			ips:         []net.IP{{10, 0, 0, 1}},
			expectedURL: "grpc:10.0.0.1|911",
		},
		{
			name:        "CIDR, scheme, port specified",
			ownCIDRs:    "10.0.0.0/8",
			ownScheme:   "grpcs",
			ownPort:     "911",
			ips:         []net.IP{{10, 0, 0, 1}},
			expectedURL: "grpcs:10.0.0.1|911",
		},
		{
			name:        "CIDR IPv4+IPv6, scheme, port specified",
			ownCIDRs:    "10.0.0.0/8,2001:db8:8a2e:370::7334/64",
			ownScheme:   "grpcs",
			ownPort:     "911",
			ips:         []net.IP{{10, 0, 0, 1}, {0x20, 0x01, 0x0d, 0xb8, 0x8a, 0x2e, 0x03, 0x70, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x11, 0x22}},
			expectedURL: "grpcs:10.0.0.1,2001:db8:8a2e:370:809:a0b:c0d:1122|911",
		},
		{
			name:        "IPv6 CIDR, no port",
			ownCIDRs:    "2001:db8:8a2e:370::7334/64",
			ips:         []net.IP{{0x20, 0x01, 0x0d, 0xb8, 0x8a, 0x2e, 0x03, 0x70, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x11, 0x22}},
			expectedErr: "unsupported network type specified in the listener config: ",
		},
		{
			name:          "IPv6 CIDR, no port, listen addr",
			ownCIDRs:      "2001:db8:8a2e:370::7334/64",
			listenNetwork: "tcp",
			listenAddress: ":123",
			ips:           []net.IP{{0x20, 0x01, 0x0d, 0xb8, 0x8a, 0x2e, 0x03, 0x70, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x11, 0x22}},
			expectedURL:   "grpc:2001:db8:8a2e:370:809:a0b:c0d:1122|123",
		},
		{
			name:        "IPv6 CIDR, port",
			ownCIDRs:    "2001:db8:8a2e:370::7334/64",
			ownPort:     "911",
			ips:         []net.IP{{0x20, 0x01, 0x0d, 0xb8, 0x8a, 0x2e, 0x03, 0x70, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x11, 0x22}},
			expectedURL: "grpc:2001:db8:8a2e:370:809:a0b:c0d:1122|911",
		},
		{
			name:        "IPv6 CIDR doesn't match",
			ownCIDRs:    "2001:db8:8a2e:370::7334/64",
			ips:         []net.IP{{0xff, 0xff, 0x0d, 0xb8, 0x8a, 0x2e, 0x03, 0x70, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x11, 0x22}},
			expectedErr: "no IPs matched CIDR(s) specified in the OWN_PRIVATE_API_CIDR environment variable",
		},
		{
			name:        "IPv6 doesn't match IPv4 CIDR",
			ownCIDRs:    "10.0.0.0/8",
			ips:         []net.IP{{0xff, 0xff, 0x0d, 0xb8, 0x8a, 0x2e, 0x03, 0x70, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x11, 0x22}},
			expectedErr: "no IPs matched CIDR(s) specified in the OWN_PRIVATE_API_CIDR environment variable",
		},
		{
			name:        "IPv4 CIDR doesn't match IPv6",
			ownCIDRs:    "10.0.0.0/8",
			ips:         []net.IP{{0xff, 0xff, 0x0d, 0xb8, 0x8a, 0x2e, 0x03, 0x70, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x11, 0x22}},
			expectedErr: "no IPs matched CIDR(s) specified in the OWN_PRIVATE_API_CIDR environment variable",
		},
		{
			name:          "IPv4 wildcard + tcp matches IPv6",
			listenNetwork: "tcp",
			listenAddress: "0.0.0.0:123",
			ips:           []net.IP{{0xff, 0xff, 0x0d, 0xb8, 0x8a, 0x2e, 0x03, 0x70, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x11, 0x22}},
			expectedURL:   "grpc:ffff:db8:8a2e:370:809:a0b:c0d:1122|123",
		},
		{
			name:          "IPv6 wildcard + tcp matches IPv4",
			listenNetwork: "tcp",
			listenAddress: "[::]:123",
			ips:           []net.IP{{10, 0, 0, 1}},
			expectedURL:   "grpc:10.0.0.1|123",
		},
		{
			name:          "loopback and link-local addresses are ignored",
			listenNetwork: "tcp",
			listenAddress: ":123",
			ips: []net.IP{
				{0xfe, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01},
				{169, 254, 1, 2},
				{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01},
				{127, 0, 0, 1},
			},
			expectedErr: "cannot determine URL - no matching network interfaces found. Please set OWN_PRIVATE_API_URL environment variable",
		},
		{
			name:          "tcp4 matches IPv4 only",
			listenNetwork: "tcp4",
			listenAddress: ":123",
			ips:           []net.IP{{10, 0, 0, 1}, {0xff, 0xff, 0x0d, 0xb8, 0x8a, 0x2e, 0x03, 0x70, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x11, 0x22}},
			expectedURL:   "grpc:10.0.0.1|123",
		},
		{
			name:          "tcp6 matches IPv6 only",
			listenNetwork: "tcp6",
			listenAddress: ":123",
			ips:           []net.IP{{10, 0, 0, 1}, {0x20, 0x01, 0x0d, 0xb8, 0x8a, 0x2e, 0x03, 0x70, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x11, 0x22}},
			expectedURL:   "grpc:2001:db8:8a2e:370:809:a0b:c0d:1122|123",
		},
		{
			name:          "tcp4 wildcard matches IPv4 only",
			listenNetwork: "tcp4",
			listenAddress: ":123",
			ips:           []net.IP{{10, 0, 0, 1}, {0xff, 0xff, 0x0d, 0xb8, 0x8a, 0x2e, 0x03, 0x70, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x11, 0x22}},
			expectedURL:   "grpc:10.0.0.1|123",
		},
		{
			name:          "tcp6 wildcard matches IPv6 only",
			listenNetwork: "tcp6",
			listenAddress: ":123",
			ips:           []net.IP{{10, 0, 0, 1}, {0x20, 0x01, 0x0d, 0xb8, 0x8a, 0x2e, 0x03, 0x70, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x11, 0x22}},
			expectedURL:   "grpc:2001:db8:8a2e:370:809:a0b:c0d:1122|123",
		},
		{
			name:          "no CIDR",
			listenNetwork: "tcp",
			listenAddress: "10.1.2.3:9090",
			expectedURL:   "grpc:10.1.2.3|9090",
		},
		{
			name:          "no CIDR, host",
			listenNetwork: "tcp",
			listenAddress: "localhost:9090",
			expectedURL:   "grpc:/localhost/|9090",
		},
		{
			name:          "no CIDR, port",
			ownPort:       "123",
			listenNetwork: "tcp",
			listenAddress: "10.1.2.3:9090",
			expectedURL:   "grpc:10.1.2.3|123",
		},
		{
			name:          "no CIDR, host, port",
			ownPort:       "123",
			listenNetwork: "tcp",
			listenAddress: "localhost:9090",
			expectedURL:   "grpc:/localhost/|123",
		},
		{
			name:          "no CIDR, scheme",
			ownScheme:     "grpcs",
			ownPort:       "123",
			listenNetwork: "tcp",
			listenAddress: "10.1.2.3:9090",
			expectedURL:   "grpcs:10.1.2.3|123",
		},
		{
			name:          "no CIDR, scheme, host",
			ownScheme:     "grpcs",
			ownPort:       "123",
			listenNetwork: "tcp",
			listenAddress: "example.com:9090",
			expectedURL:   "grpcs:/example.com/|123",
		},
		{
			name:          "no CIDR, scheme, host, TLS host",
			ownScheme:     "grpcs",
			ownTLSHost:    "example2.com",
			ownPort:       "123",
			listenNetwork: "tcp",
			listenAddress: "example.com:9090",
			expectedURL:   "grpcs:/example.com/example2.com|123",
		},
		{
			name:        "URL and CIDR",
			ownURL:      "grpc://127.0.0.1:900",
			ownCIDRs:    "10.0.0.0/8",
			expectedErr: "either OWN_PRIVATE_API_URL or OWN_PRIVATE_API_CIDR should be specified, not both",
		},
		{
			name:        "loopback 1 in URL",
			ownURL:      "grpc://127.0.0.1:900",
			expectedURL: "grpc:127.0.0.1|900",
		},
		{
			name:        "loopback 2 in URL",
			ownURL:      "grpc://127.0.0.3:900",
			expectedURL: "grpc:127.0.0.3|900",
		},
		{
			name:        "IPv4 link-local in URL",
			ownURL:      "grpc://169.254.0.1:900",
			expectedURL: "grpc:169.254.0.1|900",
		},
		{
			name:        "IPv6 link-local in URL",
			ownURL:      "grpc://[fe80::1]:900",
			expectedURL: "grpc:fe80::1|900",
		},
		{
			name:          "TLS listen",
			listenNetwork: "tcp",
			listenAddress: "10.1.2.3:9090",
			isTLS:         true,
			expectedURL:   "grpcs:10.1.2.3|9090",
		},
	}

	for _, tc := range tests {
		t.Run(tc.name, func(t *testing.T) {
			ou := privateAPIServerURL{
				log: testlogger.New(t),
				interfaceAddrs: func() ([]net.Addr, error) {
					res := make([]net.Addr, 0, len(tc.ips))
					for _, ip := range tc.ips {
						res = append(res, &net.IPNet{
							IP: ip,
							// Mask: nil unused
						})
					}
					return res, nil
				},
			}
			ownURL, err := ou.construct(tc.ownURL, tc.ownCIDRs, tc.ownScheme, tc.ownTLSHost, tc.ownPort, tc.listenNetwork, tc.listenAddress, tc.isTLS)
			if tc.expectedErr != "" {
				assert.EqualError(t, err, tc.expectedErr)
			} else {
				require.NoError(t, err)
				assert.Equal(t, tc.expectedURL, ownURL.String())
			}
		})
	}
}
