/*
Copyright The Helm Authors.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package copystructure

import (
	"testing"

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

func TestCopy_Nil(t *testing.T) {
	result, err := Copy(nil)
	require.NoError(t, err)
	assert.Equal(t, map[string]any{}, result)
}

func TestCopy_PrimitiveTypes(t *testing.T) {
	tests := []struct {
		name  string
		input any
	}{
		{"bool", true},
		{"int", 42},
		{"int8", int8(8)},
		{"int16", int16(16)},
		{"int32", int32(32)},
		{"int64", int64(64)},
		{"uint", uint(42)},
		{"uint8", uint8(8)},
		{"uint16", uint16(16)},
		{"uint32", uint32(32)},
		{"uint64", uint64(64)},
		{"float32", float32(3.14)},
		{"float64", 3.14159},
		{"complex64", complex64(1 + 2i)},
		{"complex128", 1 + 2i},
		{"string", "hello world"},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			result, err := Copy(tt.input)
			require.NoError(t, err)
			assert.Equal(t, tt.input, result)
		})
	}
}

func TestCopy_Array(t *testing.T) {
	input := [3]int{1, 2, 3}
	result, err := Copy(input)
	require.NoError(t, err)
	assert.Equal(t, input, result)
}

func TestCopy_Slice(t *testing.T) {
	t.Run("slice of ints", func(t *testing.T) {
		input := []int{1, 2, 3, 4, 5}
		result, err := Copy(input)
		require.NoError(t, err)

		resultSlice, ok := result.([]int)
		require.True(t, ok)
		assert.Equal(t, input, resultSlice)

		// Verify it's a deep copy by modifying original
		input[0] = 999
		assert.Equal(t, 1, resultSlice[0])
	})

	t.Run("slice of strings", func(t *testing.T) {
		input := []string{"a", "b", "c"}
		result, err := Copy(input)
		require.NoError(t, err)
		assert.Equal(t, input, result)
	})

	t.Run("nil slice", func(t *testing.T) {
		var input []int
		result, err := Copy(input)
		require.NoError(t, err)
		assert.Nil(t, result)
	})

	t.Run("slice of maps", func(t *testing.T) {
		input := []map[string]any{
			{"key1": "value1"},
			{"key2": "value2"},
		}
		result, err := Copy(input)
		require.NoError(t, err)

		resultSlice, ok := result.([]map[string]any)
		require.True(t, ok)
		assert.Equal(t, input, resultSlice)

		// Verify deep copy
		input[0]["key1"] = "modified"
		assert.Equal(t, "value1", resultSlice[0]["key1"])
	})

	t.Run("slice with nil elements", func(t *testing.T) {
		input := []any{
			"value1",
			nil,
			"value2",
		}
		result, err := Copy(input)
		require.NoError(t, err)

		resultSlice, ok := result.([]any)
		require.True(t, ok)
		assert.Equal(t, input, resultSlice)
		assert.Nil(t, resultSlice[1])
	})
}

func TestCopy_Map(t *testing.T) {
	t.Run("map[string]any", func(t *testing.T) {
		input := map[string]any{
			"string": "value",
			"int":    42,
			"bool":   true,
			"nested": map[string]any{
				"inner": "value",
			},
		}

		result, err := Copy(input)
		require.NoError(t, err)

		resultMap, ok := result.(map[string]any)
		require.True(t, ok)
		assert.Equal(t, input, resultMap)

		// Verify deep copy
		input["string"] = "modified"
		assert.Equal(t, "value", resultMap["string"])

		nestedInput := input["nested"].(map[string]any)
		nestedResult := resultMap["nested"].(map[string]any)
		nestedInput["inner"] = "modified"
		assert.Equal(t, "value", nestedResult["inner"])
	})

	t.Run("map[string]string", func(t *testing.T) {
		input := map[string]string{
			"key1": "value1",
			"key2": "value2",
		}

		result, err := Copy(input)
		require.NoError(t, err)
		assert.Equal(t, input, result)
	})

	t.Run("nil map", func(t *testing.T) {
		var input map[string]any
		result, err := Copy(input)
		require.NoError(t, err)
		assert.Nil(t, result)
	})

	t.Run("map with nil values", func(t *testing.T) {
		input := map[string]any{
			"key1": "value1",
			"key2": nil,
		}

		result, err := Copy(input)
		require.NoError(t, err)

		resultMap, ok := result.(map[string]any)
		require.True(t, ok)
		assert.Equal(t, input, resultMap)
		assert.Nil(t, resultMap["key2"])
	})
}

func TestCopy_Struct(t *testing.T) {
	type TestStruct struct {
		Name     string
		Age      int
		Active   bool
		Scores   []int
		Metadata map[string]any
	}

	input := TestStruct{
		Name:   "John",
		Age:    30,
		Active: true,
		Scores: []int{95, 87, 92},
		Metadata: map[string]any{
			"level": "advanced",
			"tags":  []string{"go", "programming"},
		},
	}

	result, err := Copy(input)
	require.NoError(t, err)

	resultStruct, ok := result.(TestStruct)
	require.True(t, ok)
	assert.Equal(t, input, resultStruct)

	// Verify deep copy
	input.Name = "Modified"
	input.Scores[0] = 999
	assert.Equal(t, "John", resultStruct.Name)
	assert.Equal(t, 95, resultStruct.Scores[0])
}

func TestCopy_Pointer(t *testing.T) {
	t.Run("pointer to int", func(t *testing.T) {
		value := 42
		input := &value

		result, err := Copy(input)
		require.NoError(t, err)

		resultPtr, ok := result.(*int)
		require.True(t, ok)
		assert.Equal(t, *input, *resultPtr)

		// Verify they point to different memory locations
		assert.NotSame(t, input, resultPtr)

		// Verify deep copy
		*input = 999
		assert.Equal(t, 42, *resultPtr)
	})

	t.Run("pointer to struct", func(t *testing.T) {
		type Person struct {
			Name string
			Age  int
		}

		input := &Person{Name: "Alice", Age: 25}

		result, err := Copy(input)
		require.NoError(t, err)

		resultPtr, ok := result.(*Person)
		require.True(t, ok)
		assert.Equal(t, *input, *resultPtr)
		assert.NotSame(t, input, resultPtr)
	})

	t.Run("nil pointer", func(t *testing.T) {
		var input *int
		result, err := Copy(input)
		require.NoError(t, err)
		assert.Nil(t, result)
	})
}

func TestCopy_Interface(t *testing.T) {
	t.Run("any with value", func(t *testing.T) {
		var input any = "hello"
		result, err := Copy(input)
		require.NoError(t, err)
		assert.Equal(t, input, result)
	})

	t.Run("nil any", func(t *testing.T) {
		var input any
		result, err := Copy(input)
		require.NoError(t, err)
		// Copy(nil) returns an empty map according to the implementation
		assert.Equal(t, map[string]any{}, result)
	})

	t.Run("any with complex value", func(t *testing.T) {
		var input any = map[string]any{
			"key": "value",
			"nested": map[string]any{
				"inner": 42,
			},
		}

		result, err := Copy(input)
		require.NoError(t, err)
		assert.Equal(t, input, result)
	})
}

func TestCopy_ComplexNested(t *testing.T) {
	input := map[string]any{
		"users": []map[string]any{
			{
				"name": "Alice",
				"age":  30,
				"addresses": []map[string]any{
					{"type": "home", "city": "NYC"},
					{"type": "work", "city": "SF"},
				},
			},
			{
				"name": "Bob",
				"age":  25,
				"addresses": []map[string]any{
					{"type": "home", "city": "LA"},
				},
			},
		},
		"metadata": map[string]any{
			"version": "1.0",
			"flags":   []bool{true, false, true},
		},
	}

	result, err := Copy(input)
	require.NoError(t, err)

	resultMap, ok := result.(map[string]any)
	require.True(t, ok)
	assert.Equal(t, input, resultMap)

	// Verify deep copy by modifying nested values
	users := input["users"].([]map[string]any)
	addresses := users[0]["addresses"].([]map[string]any)
	addresses[0]["city"] = "Modified"

	resultUsers := resultMap["users"].([]map[string]any)
	resultAddresses := resultUsers[0]["addresses"].([]map[string]any)
	assert.Equal(t, "NYC", resultAddresses[0]["city"])
}

func TestCopy_Functions(t *testing.T) {
	t.Run("function", func(t *testing.T) {
		input := func() string { return "hello" }
		result, err := Copy(input)
		require.NoError(t, err)

		// Functions should be copied as-is (same reference)
		resultFunc, ok := result.(func() string)
		require.True(t, ok)
		assert.Equal(t, input(), resultFunc())
	})

	t.Run("nil function", func(t *testing.T) {
		var input func()
		result, err := Copy(input)
		require.NoError(t, err)
		assert.Nil(t, result)
	})
}

func TestCopy_Channels(t *testing.T) {
	t.Run("channel", func(t *testing.T) {
		input := make(chan int, 1)
		input <- 42

		result, err := Copy(input)
		require.NoError(t, err)

		// Channels should be copied as-is (same reference)
		resultChan, ok := result.(chan int)
		require.True(t, ok)

		// Since channels are copied as references, verify we can read from the result channel
		value := <-resultChan
		assert.Equal(t, 42, value)
	})

	t.Run("nil channel", func(t *testing.T) {
		var input chan int
		result, err := Copy(input)
		require.NoError(t, err)
		assert.Nil(t, result)
	})
}
