// SPDX-License-Identifier: Apache-2.0
// Copyright Authors of K9s

package port_test

import (
	"context"
	"errors"
	"testing"

	"github.com/derailed/k9s/internal/port"
	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"
	"k8s.io/apimachinery/pkg/util/intstr"
)

func TestParsePFs(t *testing.T) {
	uu := map[string]struct {
		spec string
		pfs  port.PFAnns
		e    error
	}{
		"single": {
			spec: "c2::4321:1234",
			pfs: port.PFAnns{
				{Container: "c2", ContainerPort: intstr.Parse("1234"), LocalPort: "4321"},
			},
		},
		"multi": {
			spec: "c1::4321:1234,c2::6666:6543",
			pfs: port.PFAnns{
				{Container: "c1", ContainerPort: intstr.Parse("1234"), LocalPort: "4321"},
				{Container: "c2", ContainerPort: intstr.Parse("6543"), LocalPort: "6666"},
			},
		},
		"spaces": {
			spec: " c1::4321:1234 , c2::6666:6543 ",
			pfs: port.PFAnns{
				{Container: "c1", ContainerPort: intstr.Parse("1234"), LocalPort: "4321"},
				{Container: "c2", ContainerPort: intstr.Parse("6543"), LocalPort: "6666"},
			},
		},
		"plain-multi": {
			spec: "4321:1234, 6666:6543",
			pfs: port.PFAnns{
				{ContainerPort: intstr.Parse("1234"), LocalPort: "4321"},
				{ContainerPort: intstr.Parse("6543"), LocalPort: "6666"},
			},
		},
		"toast": {
			spec: "c1::p1:1234,c2::4321",
			e:    errors.New("invalid port-forward specification c1::p1:1234"),
		},
	}

	for k := range uu {
		u := uu[k]
		t.Run(k, func(t *testing.T) {
			pfs, err := port.ParsePFs(u.spec)
			assert.Equal(t, u.e, err)
			if err != nil {
				return
			}
			assert.Equal(t, u.pfs, pfs)
		})
	}
}

func TestPFsToTunnel(t *testing.T) {
	uu := map[string]struct {
		exp   string
		specs port.ContainerPortSpecs
		pts   port.PortTunnels
		e     error
	}{
		"single": {
			exp: "c2::4321:1234",
			specs: port.ContainerPortSpecs{
				{Container: "c2", PortName: "p1", PortNum: "1234"},
			},
			pts: port.PortTunnels{
				{Address: "fred", Container: "c2", ContainerPort: "1234", LocalPort: "4321"},
			},
		},
		"hosed": {
			exp: "c2::p2",
			specs: port.ContainerPortSpecs{
				{Container: "c2", PortName: "p1", PortNum: "1234"},
			},
			pts: port.PortTunnels{
				{Address: "fred", Container: "c2", ContainerPort: "1234", LocalPort: "4321"},
			},
			e: errors.New("no port number assigned"),
		},
	}

	f := func(context.Context, port.PortTunnel) bool {
		return true
	}

	for k := range uu {
		u := uu[k]
		t.Run(k, func(t *testing.T) {
			pfs, err := port.ParsePFs(u.exp)
			require.NoError(t, err)
			pts, err := pfs.ToTunnels("fred", u.specs, f)
			assert.Equal(t, u.e, err)
			if err != nil {
				return
			}
			assert.Equal(t, u.pts, pts)
		})
	}
}

func TestPFsToPortSpec(t *testing.T) {
	uu := map[string]struct {
		exp        string
		spec, port string
		specs      port.ContainerPortSpecs
		e          error
	}{
		"single": {
			exp:  "c2::4321:p2/1234",
			spec: "c2::1234",
			port: "4321",
			specs: port.ContainerPortSpecs{
				{Container: "c2", PortNum: "1234"},
			},
		},
	}

	for k := range uu {
		u := uu[k]
		t.Run(k, func(t *testing.T) {
			pfs, err := port.ParsePFs(u.exp)
			assert.Equal(t, u.e, err)
			if err != nil {
				return
			}
			spec, prt := pfs.ToPortSpec(u.specs)
			assert.Equal(t, u.spec, spec)
			assert.Equal(t, u.port, prt)
		})
	}
}

func TestToTunnels(t *testing.T) {
	uu := map[string]struct {
		specs, ports string
		tunnels      port.PortTunnels
		err          error
	}{
		"single": {
			specs: "c2::4321:p2/1234",
			ports: "4321",
			tunnels: port.PortTunnels{
				{
					Address:       "blee",
					LocalPort:     "4321",
					Container:     "c2",
					ContainerPort: "1234",
				},
			},
		},
		"multi": {
			specs: "c1::5432:2345/2345,c2::4321:p2/1234",
			ports: "5432,4321",
			tunnels: port.PortTunnels{
				{
					Address:       "blee",
					LocalPort:     "5432",
					Container:     "c1",
					ContainerPort: "2345",
				},
				{
					Address:       "blee",
					LocalPort:     "4321",
					Container:     "c2",
					ContainerPort: "1234",
				},
			},
		},
	}

	for k := range uu {
		u := uu[k]
		t.Run(k, func(t *testing.T) {
			tt, err := port.ToTunnels("blee", u.specs, u.ports)
			assert.Equal(t, u.err, err)
			if err != nil {
				return
			}
			assert.Equal(t, u.tunnels, tt)
		})
	}
}
