package validate

import (
	"testing"

	"git.perx.ru/perxis/perxis-go/pkg/schema/field"
	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"
)

func TestString(t *testing.T) {
	invalidOptionsSchema := `{
	"type": "object",
	"params": {
		"fields": {
			"required": {
				"options": {
					"err": true
				},
				"type": "string"
			}
		}
	}
}`
	requiredOptionsSchema := `{
	"type": "object",
	"params": {
		"fields": {
			"enum": {
				"options": {
					"enum": [{
							"value": "one"
						},
						{
							"value": "two"
						}
					]
				},
				"type": "string"
			}
		}
	}
}`

	validSchema := `{
	"type": "object",
	"params": {
		"fields": {
			"required": {
				"options": {
					"required": true
				},
				"type": "string"
			},
			"readonly": {
				"options": {
					"readonly": true
				},
				"type": "string"
			},
			"enum": {
				"options": {
					"enum": [{
							"name": "One",
							"value": "one"
						},
						{
							"name": "Two",
							"value": "two"
						}
					]
				},
				"type": "string"
			}
		}
	}
}`

	unknownFieldSchema := `{
	"type": "object",
	"params": {
		"fields": {
			"string": {}
		}
	}
}`

	tests := []struct {
		name    string
		field   *field.Field
		data    interface{}
		wantErr bool
	}{
		{"Length Max", field.String().AddOptions(MaxLength(5)), "1234567", true},
		{"Length Max with <nil>", field.String().AddOptions(MaxLength(5)), nil, false},
		{"Length Min", field.String().AddOptions(MinLength(10)), "1234", true},
		{"Length Min with <nil>", field.String().AddOptions(MinLength(10)), nil, false},
		{"Length MinMax", field.String().AddOptions(MaxLength(6), MinLength(2)), "1234567", true},
		{"Length MinMax", field.String().AddOptions(MaxLength(10), MinLength(7)), "123456", true},
		{"Length MinMax with <nil>", field.String().AddOptions(MaxLength(10), MinLength(7)), nil, false},
		{"Enum miss", field.String().AddOptions(Enum(EnumOpt{Name: "N 1", Value: "n1"}, EnumOpt{Name: "N 2", Value: "n2"})), "n3", true},
		{"Enum match", field.String().AddOptions(Enum(EnumOpt{Name: "N 1", Value: "n1"}, EnumOpt{Name: "N 2", Value: "n2"})), "n2", false},
		{"Invalid Schema Options", field.String().AddOptions(Schema()), invalidOptionsSchema, true},
		{"Required Schema Options", field.String().AddOptions(Schema()), requiredOptionsSchema, true},
		{"Valid Schema", field.String().AddOptions(Schema()), validSchema, false},
		{"Invalid Schema#1", field.String().AddOptions(Schema()), "test", true},
		{"Unknown Field", field.String().AddOptions(Schema()), unknownFieldSchema, false},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			got, err := field.Decode(nil, tt.field, tt.data)
			require.NoError(t, err)
			err = Validate(nil, tt.field, got)
			if tt.wantErr {
				require.Error(t, err)
			}
			if !tt.wantErr {
				require.NoError(t, err)
			}
		})
	}
}

func TestStringValidate(t *testing.T) {
	tests := []struct {
		name    string
		field   *field.Field
		data    interface{}
		wantErr bool
		error   string
	}{
		{"String Length Max", field.String().AddOptions(MaxLength(1)), "1", false, ""},
		{"String Length Min", field.String().AddOptions(MinLength(1)), "1", false, ""},
		{"Int Length Max", field.String().AddOptions(MaxLength(1)), 1, true, "validation error: incorrect type: \"int\", expected \"string\""},
		{"Int Length Min", field.String().AddOptions(MinLength(1)), 1, true, "validation error: incorrect type: \"int\", expected \"string\""},
		{"Float Length Max", field.String().AddOptions(MaxLength(1)), 1.0, true, "validation error: incorrect type: \"float64\", expected \"string\""},
		{"Float Length Min", field.String().AddOptions(MinLength(1)), 1.0, true, "validation error: incorrect type: \"float64\", expected \"string\""},
		{"Bool Length Max", field.String().AddOptions(MaxLength(1)), true, true, "validation error: incorrect type: \"bool\", expected \"string\""},
		{"Bool Length Min", field.String().AddOptions(MinLength(1)), true, true, "validation error: incorrect type: \"bool\", expected \"string\""},
		{"Array Length Max", field.String().AddOptions(MaxLength(1)), [1]string{""}, true, "validation error: incorrect type: \"array\", expected \"string\""},
		{"Array Length Min", field.String().AddOptions(MinLength(1)), [1]string{""}, true, "validation error: incorrect type: \"array\", expected \"string\""},
		{"Slice Length Max", field.String().AddOptions(MaxLength(1)), []string{""}, true, "validation error: incorrect type: \"slice\", expected \"string\""},
		{"Slice Length Min", field.String().AddOptions(MinLength(1)), []string{""}, true, "validation error: incorrect type: \"slice\", expected \"string\""},
		{"Map Length Max", field.String().AddOptions(MaxLength(1)), map[string]string{"": ""}, true, "validation error: incorrect type: \"map\", expected \"string\""},
		{"Map Length Min", field.String().AddOptions(MinLength(1)), map[string]string{"": ""}, true, "validation error: incorrect type: \"map\", expected \"string\""},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			err := Validate(nil, tt.field, tt.data)
			if tt.wantErr {
				require.Error(t, err)
				assert.EqualError(t, err, tt.error)
			}
			if !tt.wantErr {
				require.NoError(t, err)
			}
		})
	}
}