package zerocfg

import (
	"net"
	"reflect"
	"strings"
	"testing"
	"time"

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

const name = "sample.name"

type anyFn[T any] func(string, T, string, ...OptNode) *T

func regSource[T any](fn anyFn[T], v T) (reg func() any, value any, source map[string]any) {
	var def T
	reg = func() any {
		return fn(name, def, "desc")
	}

	source = map[string]any{name: v}

	return reg, v, source
}

func Test_ValueOk(t *testing.T) {
	tests := []struct {
		varType string
		init    func() (ptr func() any, val any, src map[string]any)
	}{
		{
			varType: "int",
			init: func() (func() any, any, map[string]any) {
				return regSource(Int, 5)
			},
		},
		{
			varType: "uint",
			init: func() (func() any, any, map[string]any) {
				return regSource(Uint, uint(42))
			},
		},
		{
			varType: "int32",
			init: func() (func() any, any, map[string]any) {
				return regSource(Int32, int32(123))
			},
		},
		{
			varType: "uint32",
			init: func() (func() any, any, map[string]any) {
				return regSource(Uint32, uint32(456))
			},
		},
		{
			varType: "int64",
			init: func() (func() any, any, map[string]any) {
				return regSource(Int64, int64(789))
			},
		},
		{
			varType: "uint64",
			init: func() (func() any, any, map[string]any) {
				return regSource(Uint64, uint64(1011))
			},
		},
		{
			varType: "bool true",
			init: func() (func() any, any, map[string]any) {
				return regSource(Bool, true)
			},
		},
		{
			varType: "bool false",
			init: func() (func() any, any, map[string]any) {
				return regSource(Bool, false)
			},
		},
		{
			varType: "bools",
			init: func() (func() any, any, map[string]any) {
				return regSource(Bools, []bool{true, false, true})
			},
		},
		{
			varType: "ints",
			init: func() (func() any, any, map[string]any) {
				return regSource(Ints, []int{1, 2, 3})
			},
		},
		{
			varType: "string",
			init: func() (func() any, any, map[string]any) {
				return regSource(Str, "value")
			},
		},
		{
			varType: "strings",
			init: func() (func() any, any, map[string]any) {
				return regSource(Strs, []string{"a", "b", "c"})
			},
		},
		{
			varType: "float32",
			init: func() (func() any, any, map[string]any) {
				return regSource(Float32, float32(3.14))
			},
		},
		{
			varType: "floats32",
			init: func() (func() any, any, map[string]any) {
				return regSource(Floats32, []float32{1.1, 2.2, 3.3})
			},
		},
		{
			varType: "float64",
			init: func() (func() any, any, map[string]any) {
				return regSource(Float64, 3.14159265359)
			},
		},
		{
			varType: "floats64",
			init: func() (func() any, any, map[string]any) {
				return regSource(Floats64, []float64{1.1, 2.2, 3.3})
			},
		},
		{
			varType: "duration",
			init: func() (func() any, any, map[string]any) {
				return regSource(Dur, 5*time.Second)
			},
		},
		{
			varType: "durations",
			init: func() (func() any, any, map[string]any) {
				return regSource(Durs, []time.Duration{time.Second, 2 * time.Minute, 3 * time.Hour})
			},
		},
		{
			varType: "ip",
			init: func() (func() any, any, map[string]any) {
				return regSource(ipInternal, net.ParseIP("192.168.1.1"))
			},
		},
		{
			varType: "map",
			init: func() (func() any, any, map[string]any) {
				return regSource(func(name string, value map[string]any, usage string, opts ...OptNode) *map[string]any {
					return Any(name, value, usage, newMapValue, opts...)
				}, map[string]any{"float": 1., "str": "val"})
			},
		},
	}

	dereference := func(t *testing.T, v any) any {
		val := reflect.ValueOf(v)
		require.True(t, val.Kind() == reflect.Ptr, "val must be a pointer")

		return val.Elem().Interface()
	}

	for _, tt := range tests {
		t.Run(tt.varType, func(t *testing.T) {
			c = testConfig()

			reg, expected, source := tt.init()
			ptr := reg()

			err := Parse(newMock(source))
			require.NoError(t, err)

			actual := dereference(t, ptr)
			require.EqualValues(t, expected, actual)

			// check Set and ToString is compatible
			node, ok := c.vs[name]
			require.True(t, ok)

			err = node.Value.Set(ToString(actual))
			require.NoError(t, err)

			updatedActual := dereference(t, ptr)
			require.Equal(t, actual, updatedActual)

			// check type name
			awaitedType := strings.Split(tt.varType, " ")[0]
			require.Equal(t, awaitedType, node.Value.Type())
		})
	}
}

func Test_ShowIP(t *testing.T) {
	// Save current global config
	savedC := c
	defer func() { c = savedC }()

	// Create fresh config
	c = testConfig()

	// Test IP display in Show()
	_ = IP("test.ip", "192.168.1.1", "test IP address")

	err := Parse()
	require.NoError(t, err)

	output := Show()
	require.Contains(t, output, "192.168.1.1")
	require.NotContains(t, output, "[") // Should not contain byte array representation
}

func Test_IPSlice(t *testing.T) {
	// Save current global config
	savedC := c
	defer func() { c = savedC }()

	// Create fresh config
	c = testConfig()

	// Test IP slice
	ips := IPs("servers", []string{"127.0.0.1", "192.168.1.1"}, "server IPs")

	err := Parse(newMock(map[string]any{
		"servers": `["10.0.0.1", "10.0.0.2"]`,
	}))
	require.NoError(t, err)

	require.Len(t, *ips, 2)
	require.Equal(t, "10.0.0.1", (*ips)[0].String())
	require.Equal(t, "10.0.0.2", (*ips)[1].String())
}
