package conf

import (
	"errors"
	"os"
	"reflect"
	"testing"

	"github.com/stretchr/testify/assert"
	"github.com/zeromicro/go-zero/core/fs"
	"github.com/zeromicro/go-zero/core/hash"
)

var dupErr conflictKeyError

func TestLoadConfig_notExists(t *testing.T) {
	assert.NotNil(t, Load("not_a_file", nil))
}

func TestLoadConfig_notRecogFile(t *testing.T) {
	filename, err := fs.TempFilenameWithText("hello")
	assert.Nil(t, err)
	defer os.Remove(filename)
	assert.NotNil(t, LoadConfig(filename, nil))
}

func TestConfigJson(t *testing.T) {
	tests := []string{
		".json",
		".yaml",
		".yml",
	}
	text := `{
	"a": "foo",
	"b": 1,
	"c": "${FOO}",
	"d": "abcd!@#$112"
}`
	t.Setenv("FOO", "2")

	for _, test := range tests {
		test := test
		t.Run(test, func(t *testing.T) {
			tmpfile, err := createTempFile(t, test, text)
			assert.Nil(t, err)

			var val struct {
				A string `json:"a"`
				B int    `json:"b"`
				C string `json:"c"`
				D string `json:"d"`
			}
			MustLoad(tmpfile, &val)
			assert.Equal(t, "foo", val.A)
			assert.Equal(t, 1, val.B)
			assert.Equal(t, "${FOO}", val.C)
			assert.Equal(t, "abcd!@#$112", val.D)
		})
	}
}

func TestLoadFromJsonBytesArray(t *testing.T) {
	input := []byte(`{"users": [{"name": "foo"}, {"Name": "bar"}]}`)
	var val struct {
		Users []struct {
			Name string
		}
	}

	assert.NoError(t, LoadConfigFromJsonBytes(input, &val))
	var expect []string
	for _, user := range val.Users {
		expect = append(expect, user.Name)
	}
	assert.EqualValues(t, []string{"foo", "bar"}, expect)
}

func TestConfigJson5(t *testing.T) {
	// JSON5 with comments, trailing commas, and unquoted keys
	text := `{
	// This is a comment
	a: 'foo',  // single quotes
	b: 1,
	c: "${FOO}",
	d: "abcd!@#$112",  // trailing comma
}`
	t.Setenv("FOO", "2")

	tmpfile, err := createTempFile(t, ".json5", text)
	assert.Nil(t, err)

	var val struct {
		A string `json:"a"`
		B int    `json:"b"`
		C string `json:"c"`
		D string `json:"d"`
	}
	MustLoad(tmpfile, &val)
	assert.Equal(t, "foo", val.A)
	assert.Equal(t, 1, val.B)
	assert.Equal(t, "${FOO}", val.C)
	assert.Equal(t, "abcd!@#$112", val.D)
}

func TestConfigJsonStandardParser(t *testing.T) {
	// Standard JSON uses standard JSON parser (not JSON5) for backward compatibility
	text := `{
	"a": "foo",
	"b": 1,
	"c": "${FOO}",
	"d": "abcd!@#$112"
}`
	t.Setenv("FOO", "2")

	tmpfile, err := createTempFile(t, ".json", text)
	assert.Nil(t, err)

	var val struct {
		A string `json:"a"`
		B int    `json:"b"`
		C string `json:"c"`
		D string `json:"d"`
	}
	MustLoad(tmpfile, &val)
	assert.Equal(t, "foo", val.A)
	assert.Equal(t, 1, val.B)
	assert.Equal(t, "${FOO}", val.C)
	assert.Equal(t, "abcd!@#$112", val.D)
}

func TestConfigJsonLargeIntegers(t *testing.T) {
	// Test that .json files preserve large integer precision (backward compatibility)
	text := `{
	"id": 1234567890123456789,
	"timestamp": 9223372036854775807
}`

	tmpfile, err := createTempFile(t, ".json", text)
	assert.Nil(t, err)

	var val struct {
		ID        int64 `json:"id"`
		Timestamp int64 `json:"timestamp"`
	}
	MustLoad(tmpfile, &val)
	assert.Equal(t, int64(1234567890123456789), val.ID)
	assert.Equal(t, int64(9223372036854775807), val.Timestamp)
}

func TestConfigJson5Env(t *testing.T) {
	text := `{
	// Comment with env variable
	a: "foo",
	b: 1,
	c: "${FOO}",
	d: "abcd!@#$a12 3",
}`
	t.Setenv("FOO", "2")

	tmpfile, err := createTempFile(t, ".json5", text)
	assert.Nil(t, err)

	var val struct {
		A string `json:"a"`
		B int    `json:"b"`
		C string `json:"c"`
		D string `json:"d"`
	}
	MustLoad(tmpfile, &val, UseEnv())
	assert.Equal(t, "foo", val.A)
	assert.Equal(t, 1, val.B)
	assert.Equal(t, "2", val.C)
	assert.Equal(t, "abcd!@# 3", val.D)
}

func TestLoadFromJson5Bytes(t *testing.T) {
	// Test JSON5 features: comments, trailing commas, single quotes, unquoted keys
	input := []byte(`{
		// This is a comment
		users: [
			{name: 'foo'},  // trailing comma
			{Name: "bar"},
		],
	}`)
	var val struct {
		Users []struct {
			Name string
		}
	}

	assert.NoError(t, LoadFromJson5Bytes(input, &val))
	var expect []string
	for _, user := range val.Users {
		expect = append(expect, user.Name)
	}
	assert.EqualValues(t, []string{"foo", "bar"}, expect)
}

func TestLoadFromJson5BytesError(t *testing.T) {
	// Invalid JSON5 syntax
	input := []byte(`{a: foo}`) // unquoted string value (invalid)
	var val struct {
		A string
	}

	assert.Error(t, LoadFromJson5Bytes(input, &val))
}

func TestConfigJson5LargeIntegersLimitation(t *testing.T) {
	// Document that JSON5 has precision limitations for large integers (>2^53)
	// due to JavaScript number semantics. Users should use .json for configs with large IDs.
	text := `{
	// JSON5 converts numbers to float64, which loses precision for large integers
	id: 1234567890123456789
}`

	tmpfile, err := createTempFile(t, ".json5", text)
	assert.Nil(t, err)

	var val struct {
		ID int64 `json:"id"`
	}

	// This will load; depending on the JSON5 implementation, large integers may lose precision.
	// This test documents that behavior without requiring loss of precision as an invariant.
	err = Load(tmpfile, &val)
	assert.NoError(t, err)

	t.Logf("loaded JSON5 large integer id=%d (original 1234567890123456789)", val.ID)
}

func TestConfigToml(t *testing.T) {
	text := `a = "foo"
b = 1
c = "${FOO}"
d = "abcd!@#$112"
`
	t.Setenv("FOO", "2")
	tmpfile, err := createTempFile(t, ".toml", text)
	assert.Nil(t, err)

	var val struct {
		A string `json:"a"`
		B int    `json:"b"`
		C string `json:"c"`
		D string `json:"d"`
	}
	MustLoad(tmpfile, &val)
	assert.Equal(t, "foo", val.A)
	assert.Equal(t, 1, val.B)
	assert.Equal(t, "${FOO}", val.C)
	assert.Equal(t, "abcd!@#$112", val.D)
}

func TestConfigOptional(t *testing.T) {
	text := `a = "foo"
b = 1
c = "FOO"
d = "abcd"
`
	tmpfile, err := createTempFile(t, ".toml", text)
	assert.Nil(t, err)

	var val struct {
		A string `json:"a"`
		B int    `json:"b,optional"`
		C string `json:"c,optional=B"`
		D string `json:"d,optional=b"`
	}
	if assert.NoError(t, Load(tmpfile, &val)) {
		assert.Equal(t, "foo", val.A)
		assert.Equal(t, 1, val.B)
		assert.Equal(t, "FOO", val.C)
		assert.Equal(t, "abcd", val.D)
	}
}

func TestConfigWithLower(t *testing.T) {
	text := `a = "foo"
b = 1
`
	tmpfile, err := createTempFile(t, ".toml", text)
	assert.Nil(t, err)

	var val struct {
		A string `json:"a"`
		b int
	}
	if assert.NoError(t, Load(tmpfile, &val)) {
		assert.Equal(t, "foo", val.A)
		assert.Equal(t, 0, val.b)
	}
}

func TestConfigJsonCanonical(t *testing.T) {
	text := []byte(`{"a": "foo", "B": "bar"}`)

	var val1 struct {
		A string `json:"a"`
		B string `json:"b"`
	}
	var val2 struct {
		A string
		B string
	}
	assert.NoError(t, LoadFromJsonBytes(text, &val1))
	assert.Equal(t, "foo", val1.A)
	assert.Equal(t, "bar", val1.B)
	assert.NoError(t, LoadFromJsonBytes(text, &val2))
	assert.Equal(t, "foo", val2.A)
	assert.Equal(t, "bar", val2.B)
}

func TestConfigTomlCanonical(t *testing.T) {
	text := []byte(`a = "foo"
B = "bar"`)

	var val1 struct {
		A string `json:"a"`
		B string `json:"b"`
	}
	var val2 struct {
		A string
		B string
	}
	assert.NoError(t, LoadFromTomlBytes(text, &val1))
	assert.Equal(t, "foo", val1.A)
	assert.Equal(t, "bar", val1.B)
	assert.NoError(t, LoadFromTomlBytes(text, &val2))
	assert.Equal(t, "foo", val2.A)
	assert.Equal(t, "bar", val2.B)
}

func TestConfigYamlCanonical(t *testing.T) {
	text := []byte(`a: foo
B: bar`)

	var val1 struct {
		A string `json:"a"`
		B string `json:"b"`
	}
	var val2 struct {
		A string
		B string
	}
	assert.NoError(t, LoadConfigFromYamlBytes(text, &val1))
	assert.Equal(t, "foo", val1.A)
	assert.Equal(t, "bar", val1.B)
	assert.NoError(t, LoadFromYamlBytes(text, &val2))
	assert.Equal(t, "foo", val2.A)
	assert.Equal(t, "bar", val2.B)
}

func TestConfigTomlEnv(t *testing.T) {
	text := `a = "foo"
b = 1
c = "${FOO}"
d = "abcd!@#112"
`
	t.Setenv("FOO", "2")
	tmpfile, err := createTempFile(t, ".toml", text)
	assert.Nil(t, err)

	var val struct {
		A string `json:"a"`
		B int    `json:"b"`
		C string `json:"c"`
		D string `json:"d"`
	}

	MustLoad(tmpfile, &val, UseEnv())
	assert.Equal(t, "foo", val.A)
	assert.Equal(t, 1, val.B)
	assert.Equal(t, "2", val.C)
	assert.Equal(t, "abcd!@#112", val.D)
}

func TestConfigJsonEnv(t *testing.T) {
	tests := []string{
		".json",
		".yaml",
		".yml",
	}
	text := `{
	"a": "foo",
	"b": 1,
	"c": "${FOO}",
	"d": "abcd!@#$a12 3"
}`
	t.Setenv("FOO", "2")
	for _, test := range tests {
		test := test
		t.Run(test, func(t *testing.T) {
			tmpfile, err := createTempFile(t, test, text)
			assert.Nil(t, err)

			var val struct {
				A string `json:"a"`
				B int    `json:"b"`
				C string `json:"c"`
				D string `json:"d"`
			}
			MustLoad(tmpfile, &val, UseEnv())
			assert.Equal(t, "foo", val.A)
			assert.Equal(t, 1, val.B)
			assert.Equal(t, "2", val.C)
			assert.Equal(t, "abcd!@# 3", val.D)
		})
	}
}

func TestToCamelCase(t *testing.T) {
	tests := []struct {
		input  string
		expect string
	}{
		{
			input:  "",
			expect: "",
		},
		{
			input:  "A",
			expect: "a",
		},
		{
			input:  "a",
			expect: "a",
		},
		{
			input:  "hello_world",
			expect: "hello_world",
		},
		{
			input:  "Hello_world",
			expect: "hello_world",
		},
		{
			input:  "hello_World",
			expect: "hello_world",
		},
		{
			input:  "helloWorld",
			expect: "helloworld",
		},
		{
			input:  "HelloWorld",
			expect: "helloworld",
		},
		{
			input:  "hello World",
			expect: "hello world",
		},
		{
			input:  "Hello World",
			expect: "hello world",
		},
		{
			input:  "Hello World",
			expect: "hello world",
		},
		{
			input:  "Hello World foo_bar",
			expect: "hello world foo_bar",
		},
		{
			input:  "Hello World foo_Bar",
			expect: "hello world foo_bar",
		},
		{
			input:  "Hello World Foo_bar",
			expect: "hello world foo_bar",
		},
		{
			input:  "Hello World Foo_Bar",
			expect: "hello world foo_bar",
		},
		{
			input:  "Hello.World Foo_Bar",
			expect: "hello.world foo_bar",
		},
		{
			input:  "你好 World Foo_Bar",
			expect: "你好 world foo_bar",
		},
	}

	for _, test := range tests {
		test := test
		t.Run(test.input, func(t *testing.T) {
			assert.Equal(t, test.expect, toLowerCase(test.input))
		})
	}
}

func TestLoadFromJsonBytesError(t *testing.T) {
	var val struct{}
	assert.Error(t, LoadFromJsonBytes([]byte(`hello`), &val))
}

func TestLoadFromTomlBytesError(t *testing.T) {
	var val struct{}
	assert.Error(t, LoadFromTomlBytes([]byte(`hello`), &val))
}

func TestLoadFromYamlBytesError(t *testing.T) {
	var val struct{}
	assert.Error(t, LoadFromYamlBytes([]byte(`':hello`), &val))
}

func TestLoadFromYamlBytes(t *testing.T) {
	input := []byte(`layer1:
  layer2:
    layer3: foo`)
	var val struct {
		Layer1 struct {
			Layer2 struct {
				Layer3 string
			}
		}
	}

	assert.NoError(t, LoadFromYamlBytes(input, &val))
	assert.Equal(t, "foo", val.Layer1.Layer2.Layer3)
}

func TestLoadFromYamlBytesTerm(t *testing.T) {
	input := []byte(`layer1:
  layer2:
    tls_conf: foo`)
	var val struct {
		Layer1 struct {
			Layer2 struct {
				Layer3 string `json:"tls_conf"`
			}
		}
	}

	assert.NoError(t, LoadFromYamlBytes(input, &val))
	assert.Equal(t, "foo", val.Layer1.Layer2.Layer3)
}

func TestLoadFromYamlBytesLayers(t *testing.T) {
	input := []byte(`layer1:
  layer2:
    layer3: foo`)
	var val struct {
		Value string `json:"Layer1.Layer2.Layer3"`
	}

	assert.NoError(t, LoadFromYamlBytes(input, &val))
	assert.Equal(t, "foo", val.Value)
}

func TestLoadFromYamlItemOverlay(t *testing.T) {
	type (
		Redis struct {
			Host string
			Port int
		}

		RedisKey struct {
			Redis
			Key string
		}

		Server struct {
			Redis RedisKey
		}

		TestConfig struct {
			Server
			Redis Redis
		}
	)

	input := []byte(`Redis:
  Host: localhost
  Port: 6379
  Key: test
`)

	var c TestConfig
	assert.ErrorAs(t, LoadFromYamlBytes(input, &c), &dupErr)
}

func TestLoadFromYamlItemOverlayReverse(t *testing.T) {
	type (
		Redis struct {
			Host string
			Port int
		}

		RedisKey struct {
			Redis
			Key string
		}

		Server struct {
			Redis Redis
		}

		TestConfig struct {
			Redis RedisKey
			Server
		}
	)

	input := []byte(`Redis:
  Host: localhost
  Port: 6379
  Key: test
`)

	var c TestConfig
	assert.ErrorAs(t, LoadFromYamlBytes(input, &c), &dupErr)
}

func TestLoadFromYamlItemOverlayWithMap(t *testing.T) {
	type (
		Redis struct {
			Host string
			Port int
		}

		RedisKey struct {
			Redis
			Key string
		}

		Server struct {
			Redis RedisKey
		}

		TestConfig struct {
			Server
			Redis map[string]interface{}
		}
	)

	input := []byte(`Redis:
  Host: localhost
  Port: 6379
  Key: test
`)

	var c TestConfig
	assert.ErrorAs(t, LoadFromYamlBytes(input, &c), &dupErr)
}

func TestUnmarshalJsonBytesMap(t *testing.T) {
	input := []byte(`{"foo":{"/mtproto.RPCTos": "bff.bff","bar":"baz"}}`)

	var val struct {
		Foo map[string]string
	}

	assert.NoError(t, LoadFromJsonBytes(input, &val))
	assert.Equal(t, "bff.bff", val.Foo["/mtproto.RPCTos"])
	assert.Equal(t, "baz", val.Foo["bar"])
}

func TestUnmarshalJsonBytesMapWithSliceElements(t *testing.T) {
	input := []byte(`{"foo":{"/mtproto.RPCTos": ["bff.bff", "any"],"bar":["baz", "qux"]}}`)

	var val struct {
		Foo map[string][]string
	}

	assert.NoError(t, LoadFromJsonBytes(input, &val))
	assert.EqualValues(t, []string{"bff.bff", "any"}, val.Foo["/mtproto.RPCTos"])
	assert.EqualValues(t, []string{"baz", "qux"}, val.Foo["bar"])
}

func TestUnmarshalJsonBytesMapWithSliceOfStructs(t *testing.T) {
	input := []byte(`{"foo":{
	"/mtproto.RPCTos": [{"bar": "any"}],
	"bar":[{"bar": "qux"}, {"bar": "ever"}]}}`)

	var val struct {
		Foo map[string][]struct {
			Bar string
		}
	}

	assert.NoError(t, LoadFromJsonBytes(input, &val))
	assert.Equal(t, 1, len(val.Foo["/mtproto.RPCTos"]))
	assert.Equal(t, "any", val.Foo["/mtproto.RPCTos"][0].Bar)
	assert.Equal(t, 2, len(val.Foo["bar"]))
	assert.Equal(t, "qux", val.Foo["bar"][0].Bar)
	assert.Equal(t, "ever", val.Foo["bar"][1].Bar)
}

func TestUnmarshalJsonBytesWithAnonymousField(t *testing.T) {
	type (
		Int int

		InnerConf struct {
			Name string
		}

		Conf struct {
			Int
			InnerConf
		}
	)

	var (
		input = []byte(`{"Name": "hello", "int": 3}`)
		c     Conf
	)
	assert.NoError(t, LoadFromJsonBytes(input, &c))
	assert.Equal(t, "hello", c.Name)
	assert.Equal(t, Int(3), c.Int)
}

func TestUnmarshalJsonBytesWithMapValueOfStruct(t *testing.T) {
	type (
		Value struct {
			Name string
		}

		Config struct {
			Items map[string]Value
		}
	)

	var inputs = [][]byte{
		[]byte(`{"Items": {"Key":{"Name": "foo"}}}`),
		[]byte(`{"Items": {"Key":{"Name": "foo"}}}`),
		[]byte(`{"items": {"key":{"name": "foo"}}}`),
		[]byte(`{"items": {"key":{"name": "foo"}}}`),
	}
	for _, input := range inputs {
		var c Config
		if assert.NoError(t, LoadFromJsonBytes(input, &c)) {
			assert.Equal(t, 1, len(c.Items))
			for _, v := range c.Items {
				assert.Equal(t, "foo", v.Name)
			}
		}
	}
}

func TestUnmarshalJsonBytesWithMapTypeValueOfStruct(t *testing.T) {
	type (
		Value struct {
			Name string
		}

		Map map[string]Value

		Config struct {
			Map
		}
	)

	var inputs = [][]byte{
		[]byte(`{"Map": {"Key":{"Name": "foo"}}}`),
		[]byte(`{"Map": {"Key":{"Name": "foo"}}}`),
		[]byte(`{"map": {"key":{"name": "foo"}}}`),
		[]byte(`{"map": {"key":{"name": "foo"}}}`),
	}
	for _, input := range inputs {
		var c Config
		if assert.NoError(t, LoadFromJsonBytes(input, &c)) {
			assert.Equal(t, 1, len(c.Map))
			for _, v := range c.Map {
				assert.Equal(t, "foo", v.Name)
			}
		}
	}
}

func Test_FieldOverwrite(t *testing.T) {
	t.Run("normal", func(t *testing.T) {
		type Base struct {
			Name string
		}

		type St1 struct {
			Base
			Name2 string
		}

		type St2 struct {
			Base
			Name2 string
		}

		type St3 struct {
			*Base
			Name2 string
		}

		type St4 struct {
			*Base
			Name2 *string
		}

		validate := func(val any) {
			input := []byte(`{"Name": "hello", "Name2": "world"}`)
			assert.NoError(t, LoadFromJsonBytes(input, val))
		}

		validate(&St1{})
		validate(&St2{})
		validate(&St3{})
		validate(&St4{})
	})

	t.Run("Inherit Override", func(t *testing.T) {
		type Base struct {
			Name string
		}

		type St1 struct {
			Base
			Name string
		}

		type St2 struct {
			Base
			Name int
		}

		type St3 struct {
			*Base
			Name int
		}

		type St4 struct {
			*Base
			Name *string
		}

		validate := func(val any) {
			input := []byte(`{"Name": "hello"}`)
			err := LoadFromJsonBytes(input, val)
			assert.ErrorAs(t, err, &dupErr)
			assert.Equal(t, newConflictKeyError("name").Error(), err.Error())
		}

		validate(&St1{})
		validate(&St2{})
		validate(&St3{})
		validate(&St4{})
	})

	t.Run("Inherit more", func(t *testing.T) {
		type Base1 struct {
			Name string
		}

		type St0 struct {
			Base1
			Name string
		}

		type St1 struct {
			St0
			Name string
		}

		type St2 struct {
			St0
			Name int
		}

		type St3 struct {
			*St0
			Name int
		}

		type St4 struct {
			*St0
			Name *int
		}

		validate := func(val any) {
			input := []byte(`{"Name": "hello"}`)
			err := LoadFromJsonBytes(input, val)
			assert.ErrorAs(t, err, &dupErr)
			assert.Error(t, err)
		}

		validate(&St0{})
		validate(&St1{})
		validate(&St2{})
		validate(&St3{})
		validate(&St4{})
	})
}

func TestFieldOverwriteComplicated(t *testing.T) {
	t.Run("double maps", func(t *testing.T) {
		type (
			Base1 struct {
				Values map[string]string
			}
			Base2 struct {
				Values map[string]string
			}
			Config struct {
				Base1
				Base2
			}
		)

		var c Config
		input := []byte(`{"Values": {"Key": "Value"}}`)
		assert.ErrorAs(t, LoadFromJsonBytes(input, &c), &dupErr)
	})

	t.Run("merge children", func(t *testing.T) {
		type (
			Inner1 struct {
				Name string
			}
			Inner2 struct {
				Age int
			}
			Base1 struct {
				Inner Inner1
			}
			Base2 struct {
				Inner Inner2
			}
			Config struct {
				Base1
				Base2
			}
		)

		var c Config
		input := []byte(`{"Inner": {"Name": "foo", "Age": 10}}`)
		if assert.NoError(t, LoadFromJsonBytes(input, &c)) {
			assert.Equal(t, "foo", c.Base1.Inner.Name)
			assert.Equal(t, 10, c.Base2.Inner.Age)
		}
	})

	t.Run("overwritten maps", func(t *testing.T) {
		type (
			Inner struct {
				Map map[string]string
			}
			Config struct {
				Map map[string]string
				Inner
			}
		)

		var c Config
		input := []byte(`{"Inner": {"Map": {"Key": "Value"}}}`)
		assert.ErrorAs(t, LoadFromJsonBytes(input, &c), &dupErr)
	})

	t.Run("overwritten nested maps", func(t *testing.T) {
		type (
			Inner struct {
				Map map[string]string
			}
			Middle1 struct {
				Map map[string]string
				Inner
			}
			Middle2 struct {
				Map map[string]string
				Inner
			}
			Config struct {
				Middle1
				Middle2
			}
		)

		var c Config
		input := []byte(`{"Middle1": {"Inner": {"Map": {"Key": "Value"}}}}`)
		assert.ErrorAs(t, LoadFromJsonBytes(input, &c), &dupErr)
	})

	t.Run("overwritten outer/inner maps", func(t *testing.T) {
		type (
			Inner struct {
				Map map[string]string
			}
			Middle struct {
				Inner
				Map map[string]string
			}
			Config struct {
				Middle
			}
		)

		var c Config
		input := []byte(`{"Middle": {"Inner": {"Map": {"Key": "Value"}}}}`)
		assert.ErrorAs(t, LoadFromJsonBytes(input, &c), &dupErr)
	})

	t.Run("overwritten anonymous maps", func(t *testing.T) {
		type (
			Inner struct {
				Map map[string]string
			}
			Middle struct {
				Inner
				Map map[string]string
			}
			Elem   map[string]Middle
			Config struct {
				Elem
			}
		)

		var c Config
		input := []byte(`{"Elem": {"Key": {"Inner": {"Map": {"Key": "Value"}}}}}`)
		assert.ErrorAs(t, LoadFromJsonBytes(input, &c), &dupErr)
	})

	t.Run("overwritten primitive and map", func(t *testing.T) {
		type (
			Inner struct {
				Value string
			}
			Elem  map[string]Inner
			Named struct {
				Elem string
			}
			Config struct {
				Named
				Elem
			}
		)

		var c Config
		input := []byte(`{"Elem": {"Key": {"Value": "Value"}}}`)
		assert.ErrorAs(t, LoadFromJsonBytes(input, &c), &dupErr)
	})

	t.Run("overwritten map and slice", func(t *testing.T) {
		type (
			Inner struct {
				Value string
			}
			Elem  []Inner
			Named struct {
				Elem string
			}
			Config struct {
				Named
				Elem
			}
		)

		var c Config
		input := []byte(`{"Elem": {"Key": {"Value": "Value"}}}`)
		assert.ErrorAs(t, LoadFromJsonBytes(input, &c), &dupErr)
	})

	t.Run("overwritten map and string", func(t *testing.T) {
		type (
			Elem  string
			Named struct {
				Elem string
			}
			Config struct {
				Named
				Elem
			}
		)

		var c Config
		input := []byte(`{"Elem": {"Key": {"Value": "Value"}}}`)
		assert.ErrorAs(t, LoadFromJsonBytes(input, &c), &dupErr)
	})
}

func TestLoadNamedFieldOverwritten(t *testing.T) {
	t.Run("overwritten named struct", func(t *testing.T) {
		type (
			Elem  string
			Named struct {
				Elem string
			}
			Base struct {
				Named
				Elem
			}
			Config struct {
				Val Base
			}
		)

		var c Config
		input := []byte(`{"Val": {"Elem": {"Key": {"Value": "Value"}}}}`)
		assert.ErrorAs(t, LoadFromJsonBytes(input, &c), &dupErr)
	})

	t.Run("overwritten named []struct", func(t *testing.T) {
		type (
			Elem  string
			Named struct {
				Elem string
			}
			Base struct {
				Named
				Elem
			}
			Config struct {
				Vals []Base
			}
		)

		var c Config
		input := []byte(`{"Vals": [{"Elem": {"Key": {"Value": "Value"}}}]}`)
		assert.ErrorAs(t, LoadFromJsonBytes(input, &c), &dupErr)
	})

	t.Run("overwritten named map[string]struct", func(t *testing.T) {
		type (
			Elem  string
			Named struct {
				Elem string
			}
			Base struct {
				Named
				Elem
			}
			Config struct {
				Vals map[string]Base
			}
		)

		var c Config
		input := []byte(`{"Vals": {"Key": {"Elem": {"Key": {"Value": "Value"}}}}}`)
		assert.ErrorAs(t, LoadFromJsonBytes(input, &c), &dupErr)
	})

	t.Run("overwritten named *struct", func(t *testing.T) {
		type (
			Elem  string
			Named struct {
				Elem string
			}
			Base struct {
				Named
				Elem
			}
			Config struct {
				Vals *Base
			}
		)

		var c Config
		input := []byte(`{"Vals": [{"Elem": {"Key": {"Value": "Value"}}}]}`)
		assert.ErrorAs(t, LoadFromJsonBytes(input, &c), &dupErr)
	})

	t.Run("overwritten named struct", func(t *testing.T) {
		type (
			Named struct {
				Elem string
			}
			Base struct {
				Named
				Elem Named
			}
			Config struct {
				Val Base
			}
		)

		var c Config
		input := []byte(`{"Val": {"Elem": "Value"}}`)
		assert.ErrorAs(t, LoadFromJsonBytes(input, &c), &dupErr)
	})

	t.Run("overwritten named struct", func(t *testing.T) {
		type Config struct {
			Val chan int
		}

		var c Config
		input := []byte(`{"Val": 1}`)
		assert.Error(t, LoadFromJsonBytes(input, &c))
	})
}

func TestLoadLowerMemberShouldNotConflict(t *testing.T) {
	type (
		Redis struct {
			db uint
		}

		Config struct {
			db uint
			Redis
		}
	)

	var c Config
	assert.NoError(t, LoadFromJsonBytes([]byte(`{}`), &c))
	assert.Zero(t, c.db)
	assert.Zero(t, c.Redis.db)
}

func TestFillDefaultUnmarshal(t *testing.T) {
	t.Run("nil", func(t *testing.T) {
		type St struct{}
		err := FillDefault(St{})
		assert.Error(t, err)
	})

	t.Run("not nil", func(t *testing.T) {
		type St struct{}
		err := FillDefault(&St{})
		assert.NoError(t, err)
	})

	t.Run("default", func(t *testing.T) {
		type St struct {
			A string `json:",default=a"`
			B string
		}
		var st St
		err := FillDefault(&st)
		assert.NoError(t, err)
		assert.Equal(t, st.A, "a")
	})

	t.Run("env", func(t *testing.T) {
		type St struct {
			A string `json:",default=a"`
			B string
			C string `json:",env=TEST_C"`
		}
		t.Setenv("TEST_C", "c")

		var st St
		err := FillDefault(&st)
		assert.NoError(t, err)
		assert.Equal(t, st.A, "a")
		assert.Equal(t, st.C, "c")
	})

	t.Run("has value", func(t *testing.T) {
		type St struct {
			A string `json:",default=a"`
			B string
		}
		var st = St{
			A: "b",
		}
		err := FillDefault(&st)
		assert.Error(t, err)
	})
}

func TestConfigWithJsonTag(t *testing.T) {
	t.Run("map with value", func(t *testing.T) {
		var input = []byte(`[Value]
[Value.first]
Email = "foo"
[Value.second]
Email = "bar"`)

		type Value struct {
			Email string
		}

		type Config struct {
			ValueMap map[string]Value `json:"Value"`
		}

		var c Config
		if assert.NoError(t, LoadFromTomlBytes(input, &c)) {
			assert.Len(t, c.ValueMap, 2)
		}
	})

	t.Run("map with ptr value", func(t *testing.T) {
		var input = []byte(`[Value]
[Value.first]
Email = "foo"
[Value.second]
Email = "bar"`)

		type Value struct {
			Email string
		}

		type Config struct {
			ValueMap map[string]*Value `json:"Value"`
		}

		var c Config
		if assert.NoError(t, LoadFromTomlBytes(input, &c)) {
			assert.Len(t, c.ValueMap, 2)
		}
	})

	t.Run("map with optional", func(t *testing.T) {
		var input = []byte(`[Value]
[Value.first]
Email = "foo"
[Value.second]
Email = "bar"`)

		type Value struct {
			Email string
		}

		type Config struct {
			Value map[string]Value `json:",optional"`
		}

		var c Config
		if assert.NoError(t, LoadFromTomlBytes(input, &c)) {
			assert.Len(t, c.Value, 2)
		}
	})

	t.Run("map with empty tag", func(t *testing.T) {
		var input = []byte(`[Value]
[Value.first]
Email = "foo"
[Value.second]
Email = "bar"`)

		type Value struct {
			Email string
		}

		type Config struct {
			Value map[string]Value `json:"  "`
		}

		var c Config
		if assert.NoError(t, LoadFromTomlBytes(input, &c)) {
			assert.Len(t, c.Value, 2)
		}
	})

	t.Run("multi layer map", func(t *testing.T) {
		type Value struct {
			User struct {
				Name string
			}
		}

		type Config struct {
			Value map[string]map[string]Value
		}

		var input = []byte(`
[Value.first.User1.User]
Name = "foo"
[Value.second.User2.User]
Name = "bar"
`)
		var c Config
		if assert.NoError(t, LoadFromTomlBytes(input, &c)) {
			assert.Len(t, c.Value, 2)
		}
	})
}

func Test_LoadBadConfig(t *testing.T) {
	type Config struct {
		Name string `json:"name,options=foo|bar"`
	}

	file, err := createTempFile(t, ".json", `{"name": "baz"}`)
	assert.NoError(t, err)

	var c Config
	err = Load(file, &c)
	assert.Error(t, err)
}

func Test_getFullName(t *testing.T) {
	assert.Equal(t, "a.b", getFullName("a", "b"))
	assert.Equal(t, "a", getFullName("", "a"))
}

func TestValidate(t *testing.T) {
	t.Run("normal config", func(t *testing.T) {
		var c mockConfig
		err := LoadFromJsonBytes([]byte(`{"val": "hello", "number": 8}`), &c)
		assert.NoError(t, err)
	})

	t.Run("error no int", func(t *testing.T) {
		var c mockConfig
		err := LoadFromJsonBytes([]byte(`{"val": "hello"}`), &c)
		assert.Error(t, err)
	})

	t.Run("error no string", func(t *testing.T) {
		var c mockConfig
		err := LoadFromJsonBytes([]byte(`{"number": 8}`), &c)
		assert.Error(t, err)
	})
}

func Test_buildFieldsInfo(t *testing.T) {
	type ParentSt struct {
		Name string
		M    map[string]int
	}
	tests := []struct {
		name        string
		t           reflect.Type
		ok          bool
		containsKey string
	}{
		{
			name: "normal",
			t:    reflect.TypeOf(struct{ A string }{}),
			ok:   true,
		},
		{
			name: "struct anonymous",
			t: reflect.TypeOf(struct {
				ParentSt
				Name string
			}{}),
			ok:          false,
			containsKey: newConflictKeyError("name").Error(),
		},
		{
			name: "struct ptr anonymous",
			t: reflect.TypeOf(struct {
				*ParentSt
				Name string
			}{}),
			ok:          false,
			containsKey: newConflictKeyError("name").Error(),
		},
		{
			name: "more struct anonymous",
			t: reflect.TypeOf(struct {
				Value struct {
					ParentSt
					Name string
				}
			}{}),
			ok:          false,
			containsKey: newConflictKeyError("value.name").Error(),
		},
		{
			name: "map anonymous",
			t: reflect.TypeOf(struct {
				ParentSt
				M string
			}{}),
			ok:          false,
			containsKey: newConflictKeyError("m").Error(),
		},
		{
			name: "map more anonymous",
			t: reflect.TypeOf(struct {
				Value struct {
					ParentSt
					M string
				}
			}{}),
			ok:          false,
			containsKey: newConflictKeyError("value.m").Error(),
		},
		{
			name: "struct slice anonymous",
			t: reflect.TypeOf([]struct {
				ParentSt
				Name string
			}{}),
			ok:          false,
			containsKey: newConflictKeyError("name").Error(),
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			_, err := buildFieldsInfo(tt.t, "")
			if tt.ok {
				assert.NoError(t, err)
			} else {
				assert.Error(t, err)
				assert.Equal(t, err.Error(), tt.containsKey)
			}
		})
	}
}

func createTempFile(t *testing.T, ext, text string) (string, error) {
	tmpFile, err := os.CreateTemp(os.TempDir(), hash.Md5Hex([]byte(text))+"*"+ext)
	if err != nil {
		return "", err
	}

	if err = os.WriteFile(tmpFile.Name(), []byte(text), os.ModeTemporary); err != nil {
		return "", err
	}

	filename := tmpFile.Name()
	if err = tmpFile.Close(); err != nil {
		return "", err
	}

	t.Cleanup(func() {
		_ = os.Remove(filename)
	})

	return filename, nil
}

type mockConfig struct {
	Val    string
	Number int
}

func (m mockConfig) Validate() error {
	if len(m.Val) == 0 {
		return errors.New("val is empty")
	}

	if m.Number == 0 {
		return errors.New("number is zero")
	}

	return nil
}

func TestGetFullName(t *testing.T) {
	tests := []struct {
		parent string
		child  string
		want   string
	}{
		{"", "child", "child"},
		{"parent", "child", "parent.child"},
		{"a.b", "c", "a.b.c"},
		{"root", "nested.field", "root.nested.field"},
	}

	for _, tt := range tests {
		t.Run(tt.parent+"."+tt.child, func(t *testing.T) {
			got := getFullName(tt.parent, tt.child)
			assert.Equal(t, tt.want, got)
		})
	}
}

// validatorConfig is a test config that implements Validate() for testing validation behavior
type validatorConfig struct {
	Value int `json:"value"`
}

func (v *validatorConfig) Validate() error {
	if v.Value < 10 {
		return errors.New("value must be >= 10")
	}
	return nil
}

// TestLoadValidation_WithoutEnv tests that validation is called correctly in normal loading path
func TestLoadValidation_WithoutEnv(t *testing.T) {
	tests := []struct {
		name      string
		extension string
		content   string
		wantErr   bool
		errMsg    string
	}{
		{
			name:      "json valid value",
			extension: ".json",
			content:   `{"value": 15}`,
			wantErr:   false,
		},
		{
			name:      "json invalid value",
			extension: ".json",
			content:   `{"value": 5}`,
			wantErr:   true,
			errMsg:    "value must be >= 10",
		},
		{
			name:      "yaml valid value",
			extension: ".yaml",
			content:   "value: 20\n",
			wantErr:   false,
		},
		{
			name:      "yaml invalid value",
			extension: ".yaml",
			content:   "value: 3\n",
			wantErr:   true,
			errMsg:    "value must be >= 10",
		},
		{
			name:      "toml valid value",
			extension: ".toml",
			content:   "value = 100\n",
			wantErr:   false,
		},
		{
			name:      "toml invalid value",
			extension: ".toml",
			content:   "value = 1\n",
			wantErr:   true,
			errMsg:    "value must be >= 10",
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			tmpfile, err := createTempFile(t, tt.extension, tt.content)
			assert.Nil(t, err)

			var cfg validatorConfig
			err = Load(tmpfile, &cfg)

			if tt.wantErr {
				assert.Error(t, err)
				assert.Contains(t, err.Error(), tt.errMsg)
			} else {
				assert.NoError(t, err)
			}
		})
	}
}

// TestLoadValidation_WithEnv tests that validation is called correctly with UseEnv() option
func TestLoadValidation_WithEnv(t *testing.T) {
	tests := []struct {
		name      string
		extension string
		content   string
		envValue  string
		wantErr   bool
		errMsg    string
	}{
		{
			name:      "json valid value with env",
			extension: ".json",
			content:   `{"value": ${TEST_VALUE}}`,
			envValue:  "25",
			wantErr:   false,
		},
		{
			name:      "json invalid value with env",
			extension: ".json",
			content:   `{"value": ${TEST_VALUE}}`,
			envValue:  "7",
			wantErr:   true,
			errMsg:    "value must be >= 10",
		},
		{
			name:      "yaml valid value with env",
			extension: ".yaml",
			content:   "value: ${TEST_VALUE}\n",
			envValue:  "50",
			wantErr:   false,
		},
		{
			name:      "yaml invalid value with env",
			extension: ".yaml",
			content:   "value: ${TEST_VALUE}\n",
			envValue:  "2",
			wantErr:   true,
			errMsg:    "value must be >= 10",
		},
		{
			name:      "toml valid value with env",
			extension: ".toml",
			content:   "value = ${TEST_VALUE}\n",
			envValue:  "99",
			wantErr:   false,
		},
		{
			name:      "toml invalid value with env",
			extension: ".toml",
			content:   "value = ${TEST_VALUE}\n",
			envValue:  "8",
			wantErr:   true,
			errMsg:    "value must be >= 10",
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			t.Setenv("TEST_VALUE", tt.envValue)

			tmpfile, err := createTempFile(t, tt.extension, tt.content)
			assert.Nil(t, err)

			var cfg validatorConfig
			err = Load(tmpfile, &cfg, UseEnv())

			if tt.wantErr {
				assert.Error(t, err)
				assert.Contains(t, err.Error(), tt.errMsg)
			} else {
				assert.NoError(t, err)
			}
		})
	}
}

// TestLoadValidation_Consistency verifies validation behavior is consistent between paths
func TestLoadValidation_Consistency(t *testing.T) {
	// Test that both paths (with and without UseEnv) produce the same validation results
	const validValue = 15

	formats := []struct {
		ext     string
		invalid string
		valid   string
	}{
		{".json", `{"value": 5}`, `{"value": 15}`},
		{".yaml", "value: 5\n", "value: 15\n"},
		{".toml", "value = 5\n", "value = 15\n"},
	}

	for _, format := range formats {
		t.Run("invalid_"+format.ext, func(t *testing.T) {
			// Test without UseEnv()
			tmpfile1, err := createTempFile(t, format.ext, format.invalid)
			assert.Nil(t, err)

			var cfg1 validatorConfig
			err1 := Load(tmpfile1, &cfg1)

			// Test with UseEnv()
			tmpfile2, err := createTempFile(t, format.ext, format.invalid)
			assert.Nil(t, err)

			var cfg2 validatorConfig
			err2 := Load(tmpfile2, &cfg2, UseEnv())

			// Both should fail validation
			assert.Error(t, err1, "validation should fail without UseEnv()")
			assert.Error(t, err2, "validation should fail with UseEnv()")
			assert.Contains(t, err1.Error(), "value must be >= 10")
			assert.Contains(t, err2.Error(), "value must be >= 10")
		})

		t.Run("valid_"+format.ext, func(t *testing.T) {
			// Test without UseEnv()
			tmpfile1, err := createTempFile(t, format.ext, format.valid)
			assert.Nil(t, err)

			var cfg1 validatorConfig
			err1 := Load(tmpfile1, &cfg1)

			// Test with UseEnv()
			tmpfile2, err := createTempFile(t, format.ext, format.valid)
			assert.Nil(t, err)

			var cfg2 validatorConfig
			err2 := Load(tmpfile2, &cfg2, UseEnv())

			// Both should pass validation
			assert.NoError(t, err1, "validation should pass without UseEnv()")
			assert.NoError(t, err2, "validation should pass with UseEnv()")
			assert.Equal(t, validValue, cfg1.Value)
			assert.Equal(t, validValue, cfg2.Value)
		})
	}
}
