package data

import (
	"fmt"
	"testing"

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

func TestDelete(t *testing.T) {
	tests := []struct {
		name  string
		in    interface{}
		field string
		out   interface{}
	}{
		{
			"simple",
			map[string]interface{}{"a": "1", "z": "2"},
			"a",
			map[string]interface{}{"z": "2"},
		},
		{
			"object",
			map[string]interface{}{"a": map[string]interface{}{"a": "1", "z": "2"}},
			"a",
			map[string]interface{}{},
		},
		{
			"object field",
			map[string]interface{}{"a": map[string]interface{}{"a": "1", "z": "2"}},
			"a.a",
			map[string]interface{}{"a": map[string]interface{}{"z": "2"}},
		},
		{
			"object field from map with array",
			map[string]interface{}{"a": []interface{}{
				map[string]interface{}{"a": "1", "b": "2"},
				map[string]interface{}{"a": "3", "b": "4"},
			}, "z": "2"},
			"a.a",
			map[string]interface{}{"a": []interface{}{
				map[string]interface{}{"b": "2"},
				map[string]interface{}{"b": "4"},
			}, "z": "2"},
		},
		{
			"object field from map with array of arrays",
			map[string]interface{}{"a": []interface{}{
				[]interface{}{
					map[string]interface{}{"a": "1", "b": "2"},
				}, []interface{}{
					map[string]interface{}{"a": "3", "b": "4"},
				},
			}, "z": "2"},
			"a.a",
			map[string]interface{}{"a": []interface{}{
				[]interface{}{
					map[string]interface{}{"b": "2"},
				}, []interface{}{
					map[string]interface{}{"b": "4"},
				},
			}, "z": "2"},
		},
		// Решили что автоматически удалять пустые объекты/слайсы не нужно
		//{
		//	"empty object",
		//	map[string]interface{}{"a": map[string]interface{}{"a": map[string]interface{}{}}},
		//	[]string{"a", "a"},
		//	map[string]interface{}{},
		//}, {
		//	"empty array",
		//	map[string]interface{}{"a": map[string]interface{}{"a": []interface{}{}}},
		//	[]string{"a", "a"},
		//	map[string]interface{}{},
		//},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			Delete(tt.field, tt.in)
			assert.Equal(t, tt.out, tt.in)
		})
	}
}

func TestDeleteMany(t *testing.T) {
	tests := []struct {
		name  string
		in    interface{}
		paths []string
		out   interface{}
	}{
		{
			"simple",
			map[string]interface{}{"a": "1", "z": "2", "d": "2"},
			[]string{"a", "d"},
			map[string]interface{}{"z": "2"},
		},
		{
			"object",
			map[string]interface{}{"a": map[string]interface{}{"a": "1", "z": "2"}},
			[]string{"a"},
			map[string]interface{}{},
		},
		{
			"object field",
			map[string]interface{}{"a": map[string]interface{}{"a": "1", "z": "2", "b": "4"}},
			[]string{"a.a", "a.b"},
			map[string]interface{}{"a": map[string]interface{}{"z": "2"}},
		},
		{
			"object field from map with array",
			map[string]interface{}{"a": []interface{}{
				map[string]interface{}{"a": "1", "b": "2", "c": 0},
				map[string]interface{}{"a": "3", "b": "4", "c": 0},
			}, "z": "2"},
			[]string{"a.a", "a.c"},
			map[string]interface{}{"a": []interface{}{
				map[string]interface{}{"b": "2"},
				map[string]interface{}{"b": "4"},
			}, "z": "2"},
		},
		{
			"object field from map with array of arrays",
			map[string]interface{}{"a": []interface{}{
				[]interface{}{
					map[string]interface{}{"a": "1", "b": "2"},
				}, []interface{}{
					map[string]interface{}{"a": "3", "b": "4"},
				},
			}, "z": "2"},
			[]string{"a.a"},
			map[string]interface{}{"a": []interface{}{
				[]interface{}{
					map[string]interface{}{"b": "2"},
				}, []interface{}{
					map[string]interface{}{"b": "4"},
				},
			}, "z": "2"},
		},
		{
			"empty object",
			map[string]interface{}{"a": map[string]interface{}{"a": map[string]interface{}{}}},
			[]string{"a.a", "a"},
			map[string]interface{}{},
		},
		{
			"field not exist in object",
			map[string]interface{}{"a": map[string]interface{}{"a": map[string]interface{}{}}},
			[]string{"a.b"},
			map[string]interface{}{"a": map[string]interface{}{"a": map[string]interface{}{}}},
		},
		{
			"empty array",
			map[string]interface{}{"a": map[string]interface{}{"a": []interface{}{}}},
			[]string{"a.a", "a"},
			map[string]interface{}{},
		},
		{
			"field not exist in array",
			map[string]interface{}{"a": map[string]interface{}{"a": []interface{}{}}},
			[]string{"a.b"},
			map[string]interface{}{"a": map[string]interface{}{"a": []interface{}{}}},
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			DeleteMany(tt.paths, tt.in)
			assert.Equal(t, tt.out, tt.in)
		})
	}
}

func TestSearch(t *testing.T) {
	tests := []struct {
		name string
		in   interface{}
		path []string
		out  interface{}
	}{
		{
			"simple",
			map[string]interface{}{"a": "1", "z": "2"},
			[]string{"a"},
			"1",
		},
		{
			"object",
			map[string]interface{}{
				"a": map[string]interface{}{"a": "1", "z": "2"},
				"b": map[string]interface{}{"c": "1", "d": "2"},
			},
			[]string{"a"},
			map[string]interface{}{"a": "1", "z": "2"},
		},
		{
			"object field",
			map[string]interface{}{"a": map[string]interface{}{"a": "1", "z": "2"}},
			[]string{"a", "a"},
			"1",
		},
		{
			"object field from map with array",
			map[string]interface{}{"a": []interface{}{
				map[string]interface{}{"a": "1", "b": "2"},
				map[string]interface{}{"a": "3", "b": "4"},
			}, "z": "2"},
			[]string{"a", "a"},
			[]interface{}{"1", "3"},
		},
		{
			"object field from array of arrays",
			[]interface{}{
				[]interface{}{
					map[string]interface{}{"a": "1", "b": "2"},
				}, []interface{}{
					map[string]interface{}{"a": "3", "b": "4"},
				},
			},
			[]string{"a"},
			[]interface{}{[]interface{}{"1"}, []interface{}{"3"}},
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			out := Search(tt.in, tt.path)
			assert.Equal(t, tt.out, out)
		})
	}
}

func TestSet(t *testing.T) {
	type args struct {
		field string
		data  any
		value any
	}
	tests := []struct {
		name     string
		args     args
		wantData any
		wantErr  assert.ErrorAssertionFunc
	}{
		{"Simple", args{"a", map[string]interface{}{"a": "0"}, "a"}, map[string]interface{}{"a": "a"}, assert.NoError},
		{"New key", args{"b", map[string]interface{}{"a": "0"}, "a"}, map[string]interface{}{"a": "0", "b": "a"}, assert.NoError},
		{"Path", args{"a.b.c", map[string]interface{}{"a": map[string]any{"b": map[string]any{"c": "0"}}}, "c"}, map[string]any{"a": map[string]any{"b": map[string]any{"c": "c"}}}, assert.NoError},
		{"Delete", args{"a.b", map[string]interface{}{"a": map[string]any{"b": map[string]any{"c": "0"}}}, DeleteValue}, map[string]any{"a": map[string]any{}}, assert.NoError},
		{"Create map", args{"b.a", map[string]interface{}{"a": "0"}, "a"}, map[string]interface{}{"a": "0", "b": map[string]interface{}{"a": "a"}}, assert.NoError},
		{"Map value", args{"a", map[string]interface{}{"a": "0"}, map[string]interface{}{"a": "a"}}, map[string]interface{}{"a": map[string]interface{}{"a": "a"}}, assert.NoError},
		{"Slice", args{"a.a", map[string]interface{}{"a": []any{map[string]any{"a": "0"}, map[string]any{"a": "0", "b": "b"}}}, "a"}, map[string]interface{}{"a": []any{map[string]any{"a": "a"}, map[string]any{"a": "a", "b": "b"}}}, assert.NoError},
		{"Slice", args{"a.0.a", map[string]interface{}{"a": []any{map[string]any{"a": "0"}, map[string]any{"a": "0", "b": "b"}}}, "a"}, map[string]interface{}{"a": []any{map[string]any{"a": "a"}, map[string]any{"a": "0", "b": "b"}}}, assert.NoError},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			data := tt.args.data
			tt.wantErr(t, Set(tt.args.field, data, tt.args.value), fmt.Sprintf("Set(%v, %v, %v)", tt.args.field, data, tt.args.value))
			assert.Equal(t, tt.wantData, data)
		})
	}
}

func TestGet(t *testing.T) {
	type args struct {
		field string
		data  any
	}
	tests := []struct {
		name  string
		args  args
		want  any
		found bool
	}{
		{"Direct value", args{"", 100}, 100, true},
		{"Not found", args{"a", 100}, nil, false},
		{"Simple", args{"a", map[string]any{"a": "0"}}, "0", true},
		{"Path", args{"a.b.c", map[string]any{"a": map[string]any{"b": map[string]any{"c": "c"}}}}, "c", true},
		{"Incorrect path", args{"a.b.wrong", map[string]any{"a": map[string]any{"b": map[string]any{"c": "c"}}}}, nil, false},
		{"Map value", args{"a.b", map[string]any{"a": map[string]any{"b": map[string]any{"c": "c"}}}}, map[string]any{"c": "c"}, true},
		{"Slice", args{"a.1.b", map[string]any{"a": []any{map[string]any{"b": "0"}, map[string]any{"b": "1"}}}}, "1", true},
		{"Slice out of range", args{"a.2.b", map[string]any{"a": []any{map[string]any{"b": "0"}, map[string]any{"b": "1"}}}}, nil, false},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			got, got1 := Get(tt.args.field, tt.args.data)
			assert.Equalf(t, tt.want, got, "Get(%v, %v)", tt.args.field, tt.args.data)
			assert.Equalf(t, tt.found, got1, "Get(%v, %v)", tt.args.field, tt.args.data)
		})
	}
}

func TestKeep(t *testing.T) {
	tests := []struct {
		name string
		in   interface{}
		path []string
		out  interface{}
	}{
		{
			"simple",
			map[string]interface{}{"a": "1", "z": "2"},
			[]string{"a"},
			map[string]interface{}{"a": "1"},
		},
		{
			"object",
			map[string]interface{}{"a": map[string]interface{}{"a": "1", "z": "2"}},
			[]string{"a"},
			map[string]interface{}{"a": map[string]interface{}{"a": "1", "z": "2"}},
		},
		{
			"no field",
			map[string]interface{}{"a": map[string]interface{}{"a": "1", "z": "2"}},
			[]string{"z"},
			map[string]interface{}{},
		},
		{
			"object field",
			map[string]interface{}{"a": map[string]interface{}{"a": "1", "z": "2"}},
			[]string{"a.a"},
			map[string]interface{}{"a": map[string]interface{}{"a": "1"}},
		},
		{
			"object field from map with array",
			map[string]interface{}{"a": []interface{}{
				map[string]interface{}{"a": "1", "b": "2"},
				map[string]interface{}{"a": "3", "b": "4"},
			}, "z": "2"},
			[]string{"a.a", "z"},
			map[string]interface{}{"a": []interface{}{
				map[string]interface{}{"a": "1"},
				map[string]interface{}{"a": "3"},
			}, "z": "2"},
		},
		{
			"object field from map with array of arrays",
			map[string]interface{}{"a": []interface{}{
				[]interface{}{
					map[string]interface{}{"a": "1", "b": "2"},
				}, []interface{}{
					map[string]interface{}{"a": "3", "b": "4"},
				},
			}, "z": "2"},
			[]string{"a.b", "z"},
			map[string]interface{}{"a": []interface{}{
				[]interface{}{
					map[string]interface{}{"b": "2"},
				}, []interface{}{
					map[string]interface{}{"b": "4"},
				},
			}, "z": "2"},
		},
		{
			"empty object",
			map[string]interface{}{"a": map[string]interface{}{"a": map[string]interface{}{}}},
			[]string{"a.b"},
			map[string]interface{}{"a": map[string]interface{}{}},
		}, {
			"empty array",
			map[string]interface{}{"a": map[string]interface{}{"a": []interface{}{}}},
			[]string{"a.b"},
			map[string]interface{}{"a": map[string]interface{}{}},
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			Keep(tt.path, tt.in)
			assert.Equal(t, tt.out, tt.in)
		})
	}
}