package config

import (
	"strconv"
	"testing"

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

type mock_config struct {
	NoTags          string
	Ignored         string  `ignored:"true"`
	Info            string  `info:"This is an info string."`
	Secret          string  `secret:"true"`
	Env             string  `env:"test_env"`
	Default_string  string  `default:"This is a default string."`
	Default_bool    bool    `default:"true"`
	Default_int     int     `default:"100"`
	Default_int64   int64   `default:"100"`
	Default_float64 float64 `default:"100.001"`
}

func TestGetEnv(t *testing.T) {
	var (
		expected_string        string  = "This is a default string."
		expected_bool          bool    = true
		expected_int           int     = 100
		expected_int64         int64   = 100
		expected_float64       float64 = 100.001
		expected_unset_default string  = "This is a default value."
	)

	// string
	t.Setenv("TEST_STRING", expected_string)
	test_string, err := getEnv("TEST_STRING", "This is a default string.")
	assert.NoError(t, err)
	assert.Equal(t, expected_string, test_string)

	// bool
	_, err = getEnv("TEST_STRING", expected_bool)
	assert.Error(t, err)
	t.Setenv("TEST_BOOL", strconv.FormatBool(expected_bool))
	test_bool, err := getEnv("TEST_BOOL", expected_bool)
	assert.NoError(t, err)
	assert.Equal(t, expected_bool, test_bool)

	// int
	_, err = getEnv("TEST_STRING", expected_int)
	assert.Error(t, err)
	t.Setenv("TEST_INT", strconv.FormatInt(int64(expected_int), 10))
	test_int, err := getEnv("TEST_INT", expected_int)
	assert.NoError(t, err)
	assert.Equal(t, expected_int, test_int)

	// int64
	_, err = getEnv("TEST_STRING", expected_int64)
	assert.Error(t, err)
	t.Setenv("TEST_INT64", strconv.FormatInt(expected_int64, 10))
	test_int64, err := getEnv("TEST_INT", expected_int64)
	assert.NoError(t, err)
	assert.Equal(t, expected_int64, test_int64)

	// float64
	_, err = getEnv("TEST_STRING", expected_float64)
	assert.Error(t, err)
	t.Setenv("TEST_INT", strconv.FormatFloat(expected_float64, 'f', 3, 64))
	test_float64, err := getEnv("TEST_INT", expected_float64)
	assert.NoError(t, err)
	assert.Equal(t, expected_float64, test_float64)

	// unset or missing environment variable
	test_unset, err := getEnv("TEST_DEFAULT", expected_unset_default)
	assert.NoError(t, err)
	assert.Equal(t, expected_unset_default, test_unset)
}

func TestGetStructInfo(t *testing.T) {
	test_config := mock_config{
		NoTags:  "notags",
		Ignored: "ignored",
		Secret:  "secret",
	}

	cfgInfo, err := getStructInfo(&test_config)
	assert.NoError(t, err)

	for _, v := range cfgInfo {
		switch v.Name {
		case "Info":
			assert.Equal(t, "() This is an info string.", v.Info)
		case "Secret":
			assert.Equal(t, false, v.Secret)
		case "Env":
			assert.Equal(t, "TEST_ENV", v.Alt)
		case "Default_value":
			assert.Equal(t, "This is a default string.", v.DefaultValue)
		}
	}
}

func TestTypeConversion(t *testing.T) {
	var (
		expected_string  string  = "This is a default string."
		expected_int     int     = 100
		expected_int8    int8    = 100
		expected_int16   int16   = 100
		expected_int32   int32   = 100
		expected_int64   int64   = 100
		expected_uint    uint    = 100
		expected_uint16  uint16  = 100
		expected_uint32  uint32  = 100
		expected_uint64  uint64  = 100
		expected_float32 float32 = 100.001
		expected_float64 float64 = 100.001
		expected_bool    bool    = true
	)

	// string
	output_string, err := typeConversion("string", expected_string)
	assert.NoError(t, err)
	assert.Equal(t, expected_string, output_string)

	// int
	output_int, err := typeConversion("int", strconv.FormatInt(int64(expected_int), 10))
	assert.NoError(t, err)
	assert.Equal(t, expected_string, output_int)

	// int8
	output_int8, err := typeConversion("int8", strconv.FormatInt(int64(expected_int8), 10))
	assert.NoError(t, err)
	assert.Equal(t, expected_int8, output_int8)

	// int16
	output_int16, err := typeConversion("int16", strconv.FormatInt(int64(expected_int16), 10))
	assert.NoError(t, err)
	assert.Equal(t, expected_int16, output_int16)

	// int32
	output_int32, err := typeConversion("int32", strconv.FormatInt(int64(expected_int32), 10))
	assert.NoError(t, err)
	assert.Equal(t, expected_int32, output_int32)

	// int64
	output_int64, err := typeConversion("int64", strconv.FormatInt(expected_int64, 10))
	assert.NoError(t, err)
	assert.Equal(t, expected_int64, output_int64)

	// uint
	output_uint, err := typeConversion("uint", strconv.FormatInt(int64(expected_uint), 10))
	assert.NoError(t, err)
	assert.Equal(t, expected_uint, output_uint)

	// uint16
	output_uint16, err := typeConversion("uint16", strconv.FormatInt(int64(expected_uint16), 10))
	assert.NoError(t, err)
	assert.Equal(t, expected_uint16, output_uint16)

	// uint32
	output_uint32, err := typeConversion("uint32", strconv.FormatInt(int64(expected_uint32), 10))
	assert.NoError(t, err)
	assert.Equal(t, expected_uint32, output_uint32)

	// uint64
	output_uint64, err := typeConversion("uint64", strconv.FormatInt(int64(expected_uint64), 10))
	assert.NoError(t, err)
	assert.Equal(t, expected_uint64, output_uint64)

	// float32
	output_float32, err := typeConversion("float32", strconv.FormatFloat(float64(expected_float32), 'f', 3, 64))
	assert.NoError(t, err)
	assert.Equal(t, expected_float32, output_float32)

	// float64
	output_float64, err := typeConversion("float64", strconv.FormatFloat(expected_float64, 'f', 3, 64))
	assert.NoError(t, err)
	assert.Equal(t, expected_float64, output_float64)

	// bool
	output_bool, err := typeConversion("bool", strconv.FormatBool(expected_bool))
	assert.NoError(t, err)
	assert.Equal(t, expected_bool, output_bool)
}

func TestParseFlags(t *testing.T) {
	test_config := Config{}

	cfgInfo, err := getStructInfo(&test_config)
	assert.NoError(t, err)

	assert.NoError(t, test_config.parseFlags(cfgInfo))
}