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

package view

import (
	"context"
	"errors"
	"log/slog"
	"testing"

	"github.com/derailed/k9s/internal"
	"github.com/derailed/k9s/internal/client"
	"github.com/derailed/k9s/internal/config"
	"github.com/derailed/k9s/internal/config/mock"
	"github.com/derailed/k9s/internal/model1"
	"github.com/derailed/k9s/internal/render"
	"github.com/derailed/tcell/v2"
	"github.com/sahilm/fuzzy"
	"github.com/stretchr/testify/assert"
	"k8s.io/cli-runtime/pkg/genericclioptions"
)

func init() {
	slog.SetDefault(slog.New(slog.DiscardHandler))
}

func TestParsePFAnn(t *testing.T) {
	uu := map[string]struct {
		ann, co, port string
		ok            bool
	}{
		"named-port": {
			ann:  "c1:blee",
			co:   "c1",
			port: "blee",
			ok:   true,
		},
		"port-num": {
			ann:  "c1:1234",
			co:   "c1",
			port: "1234",
			ok:   true,
		},
		"toast": {
			ann: "zorg",
		},
	}

	for k := range uu {
		u := uu[k]
		t.Run(k, func(t *testing.T) {
			co, port, ok := parsePFAnn(u.ann)
			if u.ok {
				assert.Equal(t, u.co, co)
				assert.Equal(t, u.port, port)
				assert.Equal(t, u.ok, ok)
			}
		})
	}
}

func TestExtractApp(t *testing.T) {
	app := NewApp(mock.NewMockConfig(t))

	uu := map[string]struct {
		app *App
		err error
	}{
		"cool":     {app: app},
		"not-cool": {err: errors.New("no application found in context")},
	}

	for k := range uu {
		u := uu[k]
		ctx := context.Background()
		if u.app != nil {
			ctx = context.WithValue(ctx, internal.KeyApp, u.app)
		}
		t.Run(k, func(t *testing.T) {
			app, err := extractApp(ctx)
			assert.Equal(t, u.err, err)
			if err == nil {
				assert.Equal(t, u.app, app)
			}
		})
	}
}

func TestFwFQN(t *testing.T) {
	uu := map[string]struct {
		po, co, e string
	}{
		"cool": {po: "p1", co: "c1", e: "p1|c1"},
	}

	for k := range uu {
		u := uu[k]
		t.Run(k, func(t *testing.T) {
			assert.Equal(t, u.e, fwFQN(u.po, u.co))
		})
	}
}

func TestAsKey(t *testing.T) {
	uu := map[string]struct {
		k   string
		err error
		e   tcell.Key
	}{
		"cool": {k: "Ctrl-A", e: tcell.KeyCtrlA},
		"miss": {k: "fred", e: 0, err: errors.New(`invalid key specified: "fred"`)},
	}

	for k := range uu {
		u := uu[k]
		t.Run(k, func(t *testing.T) {
			key, err := asKey(u.k)
			assert.Equal(t, u.err, err)
			assert.Equal(t, u.e, key)
		})
	}
}

func TestK8sEnv(t *testing.T) {
	cl, ctx, cfg, u := "cluster1", "context1", "cfg1", "user1"
	flags := genericclioptions.ConfigFlags{
		ClusterName:  &cl,
		Context:      &ctx,
		AuthInfoName: &u,
		KubeConfig:   &cfg,
	}
	c := client.NewConfig(&flags)
	env := k8sEnv(c)

	assert.Len(t, env, 5)
	assert.Equal(t, cl, env["CLUSTER"])
	assert.Equal(t, ctx, env["CONTEXT"])
	assert.Equal(t, u, env["USER"])
	assert.Equal(t, render.NAValue, env["GROUPS"])
	assert.Equal(t, cfg, env["KUBECONFIG"])
}

func TestK9sEnv(t *testing.T) {
	cl, ctx, cfg, u := "cluster1", "context1", "cfg1", "user1"
	flags := genericclioptions.ConfigFlags{
		ClusterName:  &cl,
		Context:      &ctx,
		AuthInfoName: &u,
		KubeConfig:   &cfg,
	}
	c := client.NewConfig(&flags)
	h := model1.Header{
		{Name: "A"},
		{Name: "B"},
		{Name: "C"},
	}
	r := model1.Row{
		Fields: []string{"a1", "b1", "c1"},
	}
	env := defaultEnv(c, "fred/blee", h, &r)

	assert.Len(t, env, 10)
	assert.Equal(t, cl, env["CLUSTER"])
	assert.Equal(t, ctx, env["CONTEXT"])
	assert.Equal(t, u, env["USER"])
	assert.Equal(t, render.NAValue, env["GROUPS"])
	assert.Equal(t, cfg, env["KUBECONFIG"])
	assert.Equal(t, "fred", env["NAMESPACE"])
	assert.Equal(t, "blee", env["NAME"])
	assert.Equal(t, "a1", env["COL-A"])
	assert.Equal(t, "b1", env["COL-B"])
	assert.Equal(t, "c1", env["COL-C"])
}

func TestIsTCPPort(t *testing.T) {
	uu := map[string]struct {
		p string
		e bool
	}{
		"tcp": {"80╱TCP", true},
		"udp": {"80╱UDP", false},
	}

	for k := range uu {
		u := uu[k]
		t.Run(k, func(t *testing.T) {
			assert.Equal(t, u.e, isTCPPort(u.p))
		})
	}
}

func TestFQN(t *testing.T) {
	uu := map[string]struct {
		ns, n, e string
	}{
		"fullFQN": {"blee", "fred", "blee/fred"},
		"allNS":   {"", "fred", "fred"},
	}

	for k := range uu {
		u := uu[k]
		t.Run(k, func(t *testing.T) {
			assert.Equal(t, u.e, fqn(u.ns, u.n))
		})
	}
}

func TestUrlFor(t *testing.T) {
	uu := map[string]struct {
		cfg      config.BenchConfig
		co, port string
		e        string
	}{
		"empty": {
			config.BenchConfig{}, "c1", "9000", "http://localhost:9000/",
		},
		"path": {
			config.BenchConfig{
				HTTP: config.HTTP{
					Path: "/fred/blee",
				},
			},
			"c1",
			"9000",
			"http://localhost:9000/fred/blee",
		},
		"host/path": {
			config.BenchConfig{
				HTTP: config.HTTP{
					Host: "zorg",
					Path: "/fred/blee",
				},
			},
			"c1",
			"9000",
			"http://zorg:9000/fred/blee",
		},
	}

	for k := range uu {
		u := uu[k]
		t.Run(k, func(t *testing.T) {
			assert.Equal(t, u.e, urlFor(&u.cfg, u.port))
		})
	}
}

func TestContainerID(t *testing.T) {
	uu := map[string]struct {
		path, co string
		e        string
	}{
		"plain": {
			"fred/blee", "c1", "fred/blee:c1",
		},
		"podID": {
			"fred/blee-78f8b5d78c-f8588", "c1", "fred/blee:c1",
		},
		"stsID": {
			"fred/blee-1", "c1", "fred/blee:c1",
		},
	}

	for k := range uu {
		u := uu[k]
		t.Run(k, func(t *testing.T) {
			assert.Equal(t, u.e, containerID(u.path, u.co))
		})
	}
}

func Test_linesWithRegions(t *testing.T) {
	uu := map[string]struct {
		lines   []string
		matches fuzzy.Matches
		e       []string
	}{
		"empty-lines": {
			e: []string{},
		},
		"no-match": {
			lines: []string{"bar"},
			e:     []string{"bar"},
		},
		"single-match": {
			lines: []string{"foo", "bar", "baz"},
			matches: fuzzy.Matches{
				{Index: 1, MatchedIndexes: []int{0, 1, 2}},
			},
			e: []string{"foo", matchTag(0, "bar"), "baz"},
		},
		"single-character": {
			lines: []string{"foo", "bar", "baz"},
			matches: fuzzy.Matches{
				{Index: 1, MatchedIndexes: []int{1}},
			},
			e: []string{"foo", "b" + matchTag(0, "a") + "r", "baz"},
		},
		"multiple-matches": {
			lines: []string{"foo", "bar", "baz"},
			matches: fuzzy.Matches{
				{Index: 1, MatchedIndexes: []int{0, 1, 2}},
				{Index: 2, MatchedIndexes: []int{0, 1, 2}},
			},
			e: []string{"foo", matchTag(0, "bar"), matchTag(1, "baz")},
		},
		"multiple-matches-same-line": {
			lines: []string{"foosfoo baz", "dfbarfoos bar"},
			matches: fuzzy.Matches{
				{Index: 0, MatchedIndexes: []int{0, 1, 2}},
				{Index: 0, MatchedIndexes: []int{4, 5, 6}},
				{Index: 1, MatchedIndexes: []int{5, 6, 7}},
			},
			e: []string{
				matchTag(0, "foo") + "s" + matchTag(1, "foo") + " baz",
				"dfbar" + matchTag(2, "foo") + "s bar",
			},
		},
	}

	for k := range uu {
		u := uu[k]
		t.Run(k, func(t *testing.T) {
			t.Parallel()
			assert.Equal(t, u.e, linesWithRegions(u.lines, u.matches))
		})
	}
}

func Test_sanitizeEsc(t *testing.T) {
	uu := map[string]struct {
		s string
		e string
	}{
		"empty": {},
		"empty-brackets": {
			s: "[]",
			e: "[]",
		},
		"tag": {
			s: "[fred[]",
			e: "[fred]",
		},
	}

	for k, u := range uu {
		t.Run(k, func(t *testing.T) {
			assert.Equal(t, u.e, sanitizeEsc(u.s))
		})
	}
}
