package nodegroup

import (
	"testing"

	"github.com/stretchr/testify/assert"
)

func TestDeprecatedTaint(t *testing.T) {
	taint := DeprecatedTaint()

	assert.Equal(t, TaintKeyNodeGroupState, taint.Key)
	assert.Equal(t, TaintValueDeprecated, taint.Value)
	assert.Equal(t, TaintEffectNoSchedule, taint.Effect)
}

func TestShortageFallbackTaint(t *testing.T) {
	taint := ShortageFallbackTaint()

	assert.Equal(t, TaintKeyNodeGroupState, taint.Key)
	assert.Equal(t, TaintValueShortageFallback, taint.Value)
	assert.Equal(t, TaintEffectNoSchedule, taint.Effect)
}

func TestDisabledTaint(t *testing.T) {
	taint := DisabledTaint()

	assert.Equal(t, TaintKeyNodeGroupState, taint.Key)
	assert.Equal(t, TaintValueDisabled, taint.Value)
	assert.Equal(t, TaintEffectNoSchedule, taint.Effect)
}

func TestHasTaint(t *testing.T) {
	taints := []Taint{
		{Key: "key1", Value: "val1", Effect: TaintEffectNoSchedule},
		{Key: "key2", Value: "val2", Effect: TaintEffectNoSchedule},
	}

	tests := []struct {
		name  string
		key   string
		value string
		want  bool
	}{
		{"exists", "key1", "val1", true},
		{"wrong value", "key1", "wrong", false},
		{"wrong key", "wrong", "val1", false},
		{"not exists", "key3", "val3", false},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			result := HasTaint(taints, tt.key, tt.value)
			assert.Equal(t, tt.want, result)
		})
	}
}

func TestHasTaint_EmptySlice(t *testing.T) {
	result := HasTaint(nil, "key", "value")
	assert.False(t, result)

	result = HasTaint([]Taint{}, "key", "value")
	assert.False(t, result)
}

func TestHasDeprecatedTaint(t *testing.T) {
	tests := []struct {
		name   string
		taints []Taint
		want   bool
	}{
		{
			name:   "has deprecated taint",
			taints: []Taint{DeprecatedTaint()},
			want:   true,
		},
		{
			name:   "has other taint",
			taints: []Taint{ShortageFallbackTaint()},
			want:   false,
		},
		{
			name:   "empty taints",
			taints: []Taint{},
			want:   false,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			result := HasDeprecatedTaint(tt.taints)
			assert.Equal(t, tt.want, result)
		})
	}
}

func TestHasShortageFallbackTaint(t *testing.T) {
	tests := []struct {
		name   string
		taints []Taint
		want   bool
	}{
		{
			name:   "has shortage fallback taint",
			taints: []Taint{ShortageFallbackTaint()},
			want:   true,
		},
		{
			name:   "has deprecated taint",
			taints: []Taint{DeprecatedTaint()},
			want:   false,
		},
		{
			name:   "empty taints",
			taints: []Taint{},
			want:   false,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			result := HasShortageFallbackTaint(tt.taints)
			assert.Equal(t, tt.want, result)
		})
	}
}

func TestHasDisabledTaint(t *testing.T) {
	tests := []struct {
		name   string
		taints []Taint
		want   bool
	}{
		{
			name:   "has disabled taint",
			taints: []Taint{DisabledTaint()},
			want:   true,
		},
		{
			name:   "has other taint",
			taints: []Taint{DeprecatedTaint()},
			want:   false,
		},
		{
			name:   "empty taints",
			taints: []Taint{},
			want:   false,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			result := HasDisabledTaint(tt.taints)
			assert.Equal(t, tt.want, result)
		})
	}
}

func TestMergeTaints(t *testing.T) {
	tests := []struct {
		name     string
		existing []Taint
		new      []Taint
		wantLen  int
		wantKeys []string
	}{
		{
			name:     "both empty",
			existing: []Taint{},
			new:      []Taint{},
			wantLen:  0,
			wantKeys: []string{},
		},
		{
			name:     "existing empty",
			existing: []Taint{},
			new:      []Taint{{Key: "key1", Value: "val1", Effect: TaintEffectNoSchedule}},
			wantLen:  1,
			wantKeys: []string{"key1"},
		},
		{
			name:     "new empty",
			existing: []Taint{{Key: "key1", Value: "val1", Effect: TaintEffectNoSchedule}},
			new:      []Taint{},
			wantLen:  1,
			wantKeys: []string{"key1"},
		},
		{
			name:     "no overlap",
			existing: []Taint{{Key: "key1", Value: "val1", Effect: TaintEffectNoSchedule}},
			new:      []Taint{{Key: "key2", Value: "val2", Effect: TaintEffectNoSchedule}},
			wantLen:  2,
			wantKeys: []string{"key1", "key2"},
		},
		{
			name:     "with overlap - new wins",
			existing: []Taint{{Key: "key1", Value: "oldval", Effect: TaintEffectNoSchedule}},
			new:      []Taint{{Key: "key1", Value: "newval", Effect: TaintEffectNoSchedule}},
			wantLen:  1,
			wantKeys: []string{"key1"},
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			result := MergeTaints(tt.existing, tt.new)
			assert.Len(t, result, tt.wantLen)

			resultKeys := make(map[string]bool)
			for _, taint := range result {
				resultKeys[taint.Key] = true
			}
			for _, key := range tt.wantKeys {
				assert.True(t, resultKeys[key], "expected key %s to be in result", key)
			}
		})
	}
}

func TestMergeTaints_NewWins(t *testing.T) {
	existing := []Taint{{Key: "key1", Value: "oldval", Effect: TaintEffectNoSchedule}}
	newTaints := []Taint{{Key: "key1", Value: "newval", Effect: TaintEffectNoSchedule}}

	result := MergeTaints(existing, newTaints)

	assert.Len(t, result, 1)
	assert.Equal(t, "newval", result[0].Value)
}

func TestRemoveTaint(t *testing.T) {
	tests := []struct {
		name    string
		taints  []Taint
		key     string
		wantLen int
	}{
		{
			name: "remove existing",
			taints: []Taint{
				{Key: "key1", Value: "val1"},
				{Key: "key2", Value: "val2"},
			},
			key:     "key1",
			wantLen: 1,
		},
		{
			name: "remove non-existing",
			taints: []Taint{
				{Key: "key1", Value: "val1"},
			},
			key:     "key2",
			wantLen: 1,
		},
		{
			name:    "empty taints",
			taints:  []Taint{},
			key:     "key1",
			wantLen: 0,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			result := RemoveTaint(tt.taints, tt.key)
			assert.Len(t, result, tt.wantLen)

			for _, taint := range result {
				assert.NotEqual(t, tt.key, taint.Key)
			}
		})
	}
}

func TestRemoveTaint_DoesNotMutateInput(t *testing.T) {
	original := []Taint{
		{Key: "key1", Value: "val1"},
		{Key: "key2", Value: "val2"},
	}
	originalLen := len(original)

	result := RemoveTaint(original, "key1")

	assert.Len(t, original, originalLen)
	assert.Len(t, result, originalLen-1)
}

func TestAddTaint(t *testing.T) {
	tests := []struct {
		name      string
		taints    []Taint
		newTaint  Taint
		wantLen   int
		wantAdded bool
	}{
		{
			name:      "add to empty",
			taints:    []Taint{},
			newTaint:  Taint{Key: "key1", Value: "val1", Effect: TaintEffectNoSchedule},
			wantLen:   1,
			wantAdded: true,
		},
		{
			name:      "add new taint",
			taints:    []Taint{{Key: "key1", Value: "val1", Effect: TaintEffectNoSchedule}},
			newTaint:  Taint{Key: "key2", Value: "val2", Effect: TaintEffectNoSchedule},
			wantLen:   2,
			wantAdded: true,
		},
		{
			name:      "add existing taint - same key value effect",
			taints:    []Taint{{Key: "key1", Value: "val1", Effect: TaintEffectNoSchedule}},
			newTaint:  Taint{Key: "key1", Value: "val1", Effect: TaintEffectNoSchedule},
			wantLen:   1,
			wantAdded: false,
		},
		{
			name:      "add existing key different value - replaces",
			taints:    []Taint{{Key: "key1", Value: "val1", Effect: TaintEffectNoSchedule}},
			newTaint:  Taint{Key: "key1", Value: "val2", Effect: TaintEffectNoSchedule},
			wantLen:   1,
			wantAdded: true,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			result := AddTaint(tt.taints, tt.newTaint)
			assert.Len(t, result, tt.wantLen)
		})
	}
}

func TestAddTaint_DoesNotMutateInput(t *testing.T) {
	original := []Taint{{Key: "key1", Value: "val1", Effect: TaintEffectNoSchedule}}
	originalLen := len(original)

	newTaint := Taint{Key: "key2", Value: "val2", Effect: TaintEffectNoSchedule}
	result := AddTaint(original, newTaint)

	assert.Len(t, original, originalLen)
	assert.Len(t, result, originalLen+1)
}

func TestRemoveTaintByKeyValue(t *testing.T) {
	tests := []struct {
		name     string
		taints   []Taint
		key      string
		value    string
		wantLen  int
		wantKeys []string
	}{
		{
			name: "remove exact match",
			taints: []Taint{
				{Key: "key1", Value: "val1"},
				{Key: "key2", Value: "val2"},
			},
			key:      "key1",
			value:    "val1",
			wantLen:  1,
			wantKeys: []string{"key2"},
		},
		{
			name: "same key different value preserved",
			taints: []Taint{
				{Key: TaintKeyNodeGroupState, Value: TaintValueDeprecated, Effect: TaintEffectNoSchedule},
				{Key: TaintKeyNodeGroupState, Value: TaintValueDisabled, Effect: TaintEffectNoSchedule},
			},
			key:      TaintKeyNodeGroupState,
			value:    TaintValueDisabled,
			wantLen:  1,
			wantKeys: []string{TaintKeyNodeGroupState},
		},
		{
			name: "no match by value",
			taints: []Taint{
				{Key: TaintKeyNodeGroupState, Value: TaintValueDeprecated},
			},
			key:      TaintKeyNodeGroupState,
			value:    TaintValueDisabled,
			wantLen:  1,
			wantKeys: []string{TaintKeyNodeGroupState},
		},
		{
			name:     "empty taints",
			taints:   []Taint{},
			key:      "key1",
			value:    "val1",
			wantLen:  0,
			wantKeys: nil,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			result := RemoveTaintByKeyValue(tt.taints, tt.key, tt.value)
			assert.Len(t, result, tt.wantLen)
			for i, key := range tt.wantKeys {
				assert.Equal(t, key, result[i].Key)
			}
		})
	}
}

func TestRemoveTaintByKeyValue_DoesNotMutateInput(t *testing.T) {
	original := []Taint{
		{Key: TaintKeyNodeGroupState, Value: TaintValueDeprecated},
		{Key: TaintKeyNodeGroupState, Value: TaintValueDisabled},
	}
	originalLen := len(original)

	result := RemoveTaintByKeyValue(original, TaintKeyNodeGroupState, TaintValueDisabled)

	assert.Len(t, original, originalLen)
	assert.Len(t, result, originalLen-1)
	assert.Equal(t, TaintValueDeprecated, result[0].Value)
}
