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

package model_test

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

	"github.com/derailed/k9s/internal/model"
	"github.com/derailed/k9s/internal/view/cmd"
	"github.com/derailed/tcell/v2"
	"github.com/derailed/tview"
	"github.com/stretchr/testify/assert"
	"k8s.io/apimachinery/pkg/labels"
)

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

func TestStackClear(t *testing.T) {
	comps := []model.Component{makeC("c1"), makeC("c2"), makeC("c3")}
	uu := map[string]struct {
		items []model.Component
	}{
		"empty": {
			items: []model.Component{},
		},
		"items": {
			items: comps,
		},
	}

	for k := range uu {
		u := uu[k]
		t.Run(k, func(t *testing.T) {
			s := model.NewStack()
			for _, c := range u.items {
				s.Push(c)
			}
			s.Clear()
			assert.True(t, s.Empty())
		})
	}
}

func TestStackPrevious(t *testing.T) {
	comps := []model.Component{makeC("c1"), makeC("c2"), makeC("c3")}
	uu := map[string]struct {
		items []model.Component
		pops  int
		e     model.Component
	}{
		"empty": {
			items: []model.Component{},
			pops:  0,
			e:     nil,
		},
		"one_left": {
			items: comps,
			pops:  1,
			e:     comps[0],
		},
		"none_left": {
			items: comps,
			pops:  2,
			e:     comps[0],
		},
	}

	for k := range uu {
		u := uu[k]
		t.Run(k, func(t *testing.T) {
			s := model.NewStack()
			for _, c := range u.items {
				s.Push(c)
			}
			for range u.pops {
				s.Pop()
			}
			assert.Equal(t, u.e, s.Previous())
		})
	}
}

func TestStackIsLast(t *testing.T) {
	uu := map[string]struct {
		items []model.Component
		pops  int
		e     bool
	}{
		"empty": {
			items: []model.Component{},
		},
		"normal": {
			items: []model.Component{makeC("c1"), makeC("c2"), makeC("c3")},
			pops:  1,
		},
		"last": {
			items: []model.Component{makeC("c1"), makeC("c2"), makeC("c3")},
			pops:  2,
			e:     true,
		},
	}

	for k := range uu {
		u := uu[k]
		t.Run(k, func(t *testing.T) {
			s := model.NewStack()
			for _, c := range u.items {
				s.Push(c)
			}
			for range u.pops {
				s.Pop()
			}
			assert.Equal(t, u.e, s.IsLast())
		})
	}
}

func TestStackFlatten(t *testing.T) {
	uu := map[string]struct {
		items []model.Component
		e     []string
	}{
		"empty": {
			items: []model.Component{},
			e:     []string{},
		},
		"normal": {
			items: []model.Component{makeC("c1"), makeC("c2"), makeC("c3")},
			e:     []string{"c1", "c2", "c3"},
		},
	}

	for k := range uu {
		u := uu[k]
		t.Run(k, func(t *testing.T) {
			s := model.NewStack()
			for _, c := range u.items {
				s.Push(c)
			}
			assert.Equal(t, u.e, s.Flatten())
			assert.Len(t, s.Peek(), len(u.e))
		})
	}
}

func TestStackPush(t *testing.T) {
	top := c{}
	uu := map[string]struct {
		items []model.Component
		pop   int
		e     bool
		top   model.Component
	}{
		"empty": {
			items: []model.Component{},
			pop:   3,
			e:     true,
		},
		"full": {
			items: []model.Component{c{}, c{}, top},
			pop:   3,
			e:     true,
		},
		"pop": {
			items: []model.Component{c{}, c{}, top},
			pop:   2,
			e:     false,
			top:   top,
		},
	}

	for k := range uu {
		u := uu[k]
		t.Run(k, func(t *testing.T) {
			s := model.NewStack()
			for _, c := range u.items {
				s.Push(c)
			}
			for range u.pop {
				s.Pop()
			}
			assert.Equal(t, u.e, s.Empty())
			if !u.e {
				assert.Equal(t, u.top, s.Top())
			}
		})
	}
}

func TestStackTop(t *testing.T) {
	top := c{}
	uu := map[string]struct {
		items []model.Component
		e     model.Component
	}{
		"blank": {
			items: []model.Component{},
		},
		"push3": {
			items: []model.Component{c{}, c{}, top},
			e:     top,
		},
		"push1": {
			items: []model.Component{top},
			e:     top,
		},
	}

	for k := range uu {
		u := uu[k]
		t.Run(k, func(t *testing.T) {
			s := model.NewStack()
			for _, item := range u.items {
				s.Push(item)
			}
			v := s.Top()
			assert.Equal(t, u.e, v)
		})
	}
}

func TestStackAddListener(t *testing.T) {
	items := []model.Component{c{}, c{}, c{}}
	s := model.NewStack()
	l := stackL{}
	s.AddListener(&l)
	for _, item := range items {
		s.Push(item)
	}
	assert.Equal(t, 3, l.count)

	for range items {
		s.Pop()
	}
	assert.Equal(t, 0, l.count)
}

func TestStackAddListenerAfter(t *testing.T) {
	items := []model.Component{c{}, c{}, c{}}
	s := model.NewStack()
	l := stackL{}
	for _, item := range items {
		s.Push(item)
	}
	s.AddListener(&l)
	assert.Equal(t, 1, l.tops)
	assert.Equal(t, 0, l.count)
}

func TestStackRemoveListener(t *testing.T) {
	s := model.NewStack()
	l1, l2, l3 := &stackL{}, &stackL{}, &stackL{}
	s.AddListener(l1)
	s.AddListener(l2)

	s.RemoveListener(l2)
	s.RemoveListener(l3)
	s.RemoveListener(l1)

	s.Push(c{})

	assert.Equal(t, 0, l1.count)
	assert.Equal(t, 0, l2.count)
	assert.Equal(t, 0, l3.count)
}

// ----------------------------------------------------------------------------
// Helpers...

type stackL struct {
	count, tops int
}

func (s *stackL) StackPushed(model.Component) {
	s.count++
}

func (s *stackL) StackPopped(_, _ model.Component) {
	s.count--
}

func (s *stackL) StackTop(model.Component) { s.tops++ }

type c struct {
	name string
}

func makeC(n string) c {
	return c{name: n}
}

func (c) InCmdMode() bool                                            { return false }
func (c c) Name() string                                             { return c.name }
func (c) SetCommand(*cmd.Interpreter)                                {}
func (c) Hints() model.MenuHints                                     { return nil }
func (c) HasFocus() bool                                             { return false }
func (c) ExtraHints() map[string]string                              { return nil }
func (c) Draw(tcell.Screen)                                          {}
func (c) InputHandler() func(*tcell.EventKey, func(tview.Primitive)) { return nil }
func (c) MouseHandler() func(action tview.MouseAction, event *tcell.EventMouse, setFocus func(p tview.Primitive)) (consumed bool, capture tview.Primitive) {
	return nil
}
func (c) SetRect(int, int, int, int)             {}
func (c) GetRect() (a, b, c, d int)              { return 0, 0, 0, 0 }
func (c) GetFocusable() tview.Focusable          { return nil }
func (c) Focus(func(tview.Primitive))            {}
func (c) Blur()                                  {}
func (c) Start()                                 {}
func (c) Stop()                                  {}
func (c) Init(context.Context) error             { return nil }
func (c) SetFilter(string, bool)                 {}
func (c) SetLabelSelector(labels.Selector, bool) {}
