package validation

import (
	"testing"

	ut "github.com/go-playground/universal-translator"
	valid "github.com/go-playground/validator/v10"
	"github.com/stretchr/testify/assert"
)

type testStruct struct {
	Field1 int      `json:"field1,omitempty" validate:"eq=1"`
	Field2 string   `json:"field2,omitempty" validate:"possible-values=value1 value2 value3"`
	Field3 string   `json:"field3,omitempty" validate:"required_if=Field1 1 Field4 test"`
	Field4 string   `json:"field4,omitempty"`
	Field5 []string `json:"field5,omitempty" validate:"possible-values=val1 val2 val3 val4"`
}

func TestValidateStruct(t *testing.T) {
	t.Run("success case", func(t *testing.T) {
		validation, err := New(WithPredefinedErrorMessages(), WithJSONNamesForStructFields())
		assert.NoError(t, err)
		tStruct := testStruct{
			Field1: 1,
			Field2: "value1",
			Field3: "field3",
			Field4: "test",
			Field5: []string{"val1", "val2", "val3"},
		}
		err = validation.ValidateStruct(tStruct)
		assert.NoError(t, err)
	})

	t.Run("success case - empty values for string and []string", func(t *testing.T) {
		validation, err := New(WithPredefinedErrorMessages(), WithJSONNamesForStructFields())
		assert.NoError(t, err)
		tStruct := testStruct{
			Field1: 1,
			Field3: "field3",
			Field4: "test",
		}
		err = validation.ValidateStruct(tStruct)
		assert.NoError(t, err)
	})

	t.Run("error case - default error messages", func(t *testing.T) {
		validation, err := New()
		assert.NoError(t, err)
		testStruct := testStruct{
			Field1: 1,
			Field2: "value4",
			Field4: "test",
			Field5: []string{"val1", "val2", "val5"},
		}
		err = validation.ValidateStruct(testStruct)
		assert.Error(t, err)
		assert.Contains(t, err.Error(), "Key: 'testStruct.Field2' Error:Field validation for 'Field2' failed on the 'possible-values' tag.")
		assert.Contains(t, err.Error(), "Key: 'testStruct.Field3' Error:Field validation for 'Field3' failed on the 'required_if' tag.")
		assert.Contains(t, err.Error(), "Key: 'testStruct.Field5' Error:Field validation for 'Field5' failed on the 'possible-values' tag.")
	})

	t.Run("error case - predefined error messages without naming fields from json tags", func(t *testing.T) {
		validation, err := New(WithPredefinedErrorMessages())
		assert.NoError(t, err)
		testStruct := testStruct{
			Field1: 1,
			Field2: "value4",
			Field4: "test",
			Field5: []string{"val1", "val2", "val5"},
		}
		err = validation.ValidateStruct(testStruct)
		assert.Error(t, err)
		assert.Contains(t, err.Error(), "The Field2 must use the following values: value1 value2 value3.")
		assert.Contains(t, err.Error(), "The Field3 is required since the Field1 is 1.")
		assert.Contains(t, err.Error(), "The Field5 must use the following values: val1 val2 val3 val4.")
	})

	t.Run("failed case - predefined error messages with naming fields from json tags", func(t *testing.T) {
		validation, err := New(WithPredefinedErrorMessages(), WithJSONNamesForStructFields())
		assert.NoError(t, err)
		testStruct := testStruct{
			Field1: 1,
			Field2: "value4",
			Field4: "test",
			Field5: []string{"val1", "val2", "val5"},
		}
		err = validation.ValidateStruct(testStruct)
		assert.Error(t, err)
		assert.Contains(t, err.Error(), "The field2 must use the following values: value1 value2 value3.")
		assert.Contains(t, err.Error(), "The field3 is required since the Field1 is 1.")
		assert.Contains(t, err.Error(), "The field5 must use the following values: val1 val2 val3 val4.")
	})

	t.Run("failed case - custom error messages", func(t *testing.T) {
		translations := []Translation{
			{
				Tag: "possible-values",
				RegisterFn: func(ut ut.Translator) error {
					return ut.Add("possible-values", "Custom error message for {0}", true)
				},
				TranslationFn: func(ut ut.Translator, fe valid.FieldError) string {
					t, _ := ut.T("possible-values", fe.Field())
					return t
				},
			}, {
				Tag: "required_if",
				RegisterFn: func(ut ut.Translator) error {
					return ut.Add("required_if", "Custom error message for {0}", true)
				},
				TranslationFn: func(ut ut.Translator, fe valid.FieldError) string {
					t, _ := ut.T("required_if", fe.Field())
					return t
				},
			},
		}
		validation, err := New(WithCustomErrorMessages(translations))
		assert.NoError(t, err)
		testStruct := testStruct{
			Field1: 1,
			Field2: "value4",
			Field4: "test",
			Field5: []string{"val1", "val2", "val5"},
		}
		err = validation.ValidateStruct(testStruct)
		assert.Error(t, err)
		assert.Contains(t, err.Error(), "Custom error message for Field2")
		assert.Contains(t, err.Error(), "Custom error message for Field3")
		assert.Contains(t, err.Error(), "Custom error message for Field5")
	})
}
