package localizer

import (
	"testing"

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

func TestLocalizer_localize(t *testing.T) {

	s := schema.New(
		"a", field.String(),
		"b", field.Number(field.NumberFormatInt),
		"c", field.String().SetSingleLocale(true),
		"obj1", field.Object(
			"a", field.String(),
			"b", field.Number(field.NumberFormatInt),
			"c", field.Number(field.NumberFormatInt).SetSingleLocale(true),
			"obj2", field.Object(
				"a", field.Number(field.NumberFormatInt),
				"b", field.String(),
				"c", field.String().SetSingleLocale(true),
			),
		),
		"obj3", field.Object(
			"a", field.String(),
		).SetSingleLocale(true),
		"slice", field.Array(field.String()),
		"arr", field.Array(field.Object(
			"num", field.Number(field.NumberFormatInt),
		)),
	)

	tests := []struct {
		fallback map[string]interface{}
		target   map[string]interface{}
		want     map[string]interface{}
		name     string
		wantErr  bool
	}{
		{
			name:     "Nil",
			fallback: nil,
			target:   nil,
			want:     nil,
		},
		{
			name:     "Empty",
			fallback: map[string]interface{}{},
			target:   map[string]interface{}{},
			want:     map[string]interface{}{},
		},
		{
			name: "Target Empty",
			fallback: map[string]interface{}{
				"a": "en",
				"b": 1,
				"c": "single",
				"obj1": map[string]interface{}{
					"a": "obj1_en",
					"b": 2,
					"c": 20,
					"obj2": map[string]interface{}{
						"a": 3,
						"b": "obj1_obj2_en",
						"c": "obj1_obj2_single",
					},
				},
				"obj3": map[string]interface{}{
					"a": "obj3_en",
				},
				"slice": []interface{}{"en_s1", "en_s2"},
				"arr": []interface{}{
					map[string]interface{}{"num": 11},
					map[string]interface{}{"num": 22},
				},
			},
			target: map[string]interface{}{},
			want: map[string]interface{}{
				"a": "en",
				"b": 1,
				"c": "single",
				"obj1": map[string]interface{}{
					"a": "obj1_en",
					"b": 2,
					"c": 20,
					"obj2": map[string]interface{}{
						"a": 3,
						"b": "obj1_obj2_en",
						"c": "obj1_obj2_single",
					},
				},
				"obj3": map[string]interface{}{
					"a": "obj3_en",
				},
				"slice": []interface{}{"en_s1", "en_s2"},
				"arr": []interface{}{
					map[string]interface{}{"num": 11},
					map[string]interface{}{"num": 22},
				},
			},
		},
		{
			name:     "Fallback Empty",
			fallback: map[string]interface{}{},
			target: map[string]interface{}{
				"a": "en",
				"b": 1,
				"c": "single",
				"obj1": map[string]interface{}{
					"a": "obj1_en",
					"b": 2,
					"c": 20,
					"obj2": map[string]interface{}{
						"a": 3,
						"b": "obj1_obj2_en",
						"c": "obj1_obj2_single",
					},
				},
				"obj3": map[string]interface{}{
					"a": "obj3_en",
				},
				"slice": []interface{}{"en_s1", "en_s2"},
				"arr": []interface{}{
					map[string]interface{}{"num": 11},
					map[string]interface{}{"num": 22},
				},
			},
			want: map[string]interface{}{
				"a": "en",
				"b": 1,
				"obj1": map[string]interface{}{
					"a": "obj1_en",
					"b": 2,
					"obj2": map[string]interface{}{
						"a": 3,
						"b": "obj1_obj2_en",
					},
				},
				"slice": []interface{}{"en_s1", "en_s2"},
				"arr": []interface{}{
					map[string]interface{}{"num": 11},
					map[string]interface{}{"num": 22},
				},
			},
		},
		{
			name: "Equal",
			fallback: map[string]interface{}{
				"a": "en",
				"b": 1,
				"c": "single",
				"obj1": map[string]interface{}{
					"a": "obj1_en",
					"b": 2,
					"c": 20,
					"obj2": map[string]interface{}{
						"a": 3,
						"b": "obj1_obj2_en",
						"c": "obj1_obj2_single",
					},
				},
				"obj3": map[string]interface{}{
					"a": "obj3_en",
				},
				"slice": []interface{}{"en_s1", "en_s2"},
				"arr": []interface{}{
					map[string]interface{}{"num": 11},
					map[string]interface{}{"num": 22},
				},
			},
			target: map[string]interface{}{
				"a": "en",
				"b": 1,
				"c": "single",
				"obj1": map[string]interface{}{
					"a": "obj1_en",
					"b": 2,
					"c": 20,
					"obj2": map[string]interface{}{
						"a": 3,
						"b": "obj1_obj2_en",
						"c": "obj1_obj2_single",
					},
				},
				"obj3": map[string]interface{}{
					"a": "obj3_en",
				},
				"slice": []interface{}{"en_s1", "en_s2"},
				"arr": []interface{}{
					map[string]interface{}{"num": 11},
					map[string]interface{}{"num": 22},
				},
			},
			want: map[string]interface{}{
				"a": "en",
				"b": 1,
				"c": "single",
				"obj1": map[string]interface{}{
					"a": "obj1_en",
					"b": 2,
					"c": 20,
					"obj2": map[string]interface{}{
						"a": 3,
						"b": "obj1_obj2_en",
						"c": "obj1_obj2_single",
					},
				},
				"obj3": map[string]interface{}{
					"a": "obj3_en",
				},
				"slice": []interface{}{"en_s1", "en_s2"},
				"arr": []interface{}{
					map[string]interface{}{"num": 11},
					map[string]interface{}{"num": 22},
				},
			},
		},
		{
			name: "Target no single",
			fallback: map[string]interface{}{
				"a": "en",
				"b": 1,
				"c": "single",
				"obj1": map[string]interface{}{
					"a": "obj1_en",
					"b": 2,
					"c": 20,
					"obj2": map[string]interface{}{
						"a": 3,
						"b": "obj1_obj2_en",
						"c": "obj1_obj2_single",
					},
				},
				"obj3": map[string]interface{}{
					"a": "obj3_en",
				},
				"slice": []interface{}{"en_s1", "en_s2"},
				"arr": []interface{}{
					map[string]interface{}{"num": 11},
					map[string]interface{}{"num": 22},
				},
			},
			target: map[string]interface{}{
				"a": "en",
				"b": 1,
				"obj1": map[string]interface{}{
					"a": "obj1_en",
					"b": 2,
					"obj2": map[string]interface{}{
						"a": 3,
						"b": "obj1_obj2_en",
					},
				},
				"slice": []interface{}{"en_s1", "en_s2"},
				"arr": []interface{}{
					map[string]interface{}{"num": 11},
					map[string]interface{}{"num": 22},
				},
			},
			want: map[string]interface{}{
				"a": "en",
				"b": 1,
				"c": "single",
				"obj1": map[string]interface{}{
					"a": "obj1_en",
					"b": 2,
					"c": 20,
					"obj2": map[string]interface{}{
						"a": 3,
						"b": "obj1_obj2_en",
						"c": "obj1_obj2_single",
					},
				},
				"obj3": map[string]interface{}{
					"a": "obj3_en",
				},
				"slice": []interface{}{"en_s1", "en_s2"},
				"arr": []interface{}{
					map[string]interface{}{"num": 11},
					map[string]interface{}{"num": 22},
				},
			},
		},
		{
			name: "Success",
			fallback: map[string]interface{}{
				"a": "en",
				"b": 1,
				"c": "single",
				"obj1": map[string]interface{}{
					"a": "obj1_en",
					"b": 2,
					"c": 20,
					"obj2": map[string]interface{}{
						"a": 3,
						"b": "obj1_obj2_en",
						"c": "obj1_obj2_single",
					},
				},
				"obj3": map[string]interface{}{
					"a": "obj3_en",
				},
				"slice": []interface{}{"en_s1", "en_s2"},
				"arr": []interface{}{
					map[string]interface{}{"num": 11},
					map[string]interface{}{"num": 22},
				},
			},
			target: map[string]interface{}{
				"a": "ru",
				"b": 11,
				"obj1": map[string]interface{}{
					"a": "obj1_ru",
					"b": 22,
					"obj2": map[string]interface{}{
						"a": 33,
						"b": "obj1_obj2_ru",
					},
				},
				"slice": []interface{}{"ru_s3"},
				"arr": []interface{}{
					map[string]interface{}{"num": 1},
					map[string]interface{}{"num": 2},
				},
			},
			want: map[string]interface{}{
				"a": "ru",
				"b": 11,
				"c": "single",
				"obj1": map[string]interface{}{
					"a": "obj1_ru",
					"b": 22,
					"c": 20,
					"obj2": map[string]interface{}{
						"a": 33,
						"b": "obj1_obj2_ru",
						"c": "obj1_obj2_single",
					},
				},
				"obj3": map[string]interface{}{
					"a": "obj3_en",
				},
				"slice": []interface{}{"ru_s3"},
				"arr": []interface{}{
					map[string]interface{}{"num": 1},
					map[string]interface{}{"num": 2},
				},
			},
		},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			l := &Localizer{
				schema: s,
			}
			got, err := l.localize(tt.target, tt.fallback)
			if !tt.wantErr {
				require.NoError(t, err)
				assert.Equal(t, tt.want, got)
				return
			}
			require.Error(t, err)
		})
	}
}

func TestLocalizer_deLocalize(t *testing.T) {

	s := schema.New(
		"a", field.String(),
		"b", field.Number(field.NumberFormatInt),
		"c", field.String().SetSingleLocale(true),
		"obj1", field.Object(
			"a", field.String(),
			"b", field.Number(field.NumberFormatInt),
			"c", field.Number(field.NumberFormatInt).SetSingleLocale(true),
			"obj2", field.Object(
				"a", field.Number(field.NumberFormatInt),
				"b", field.String(),
				"c", field.String().SetSingleLocale(true),
			),
		),
		"obj3", field.Object(
			"a", field.String(),
		).SetSingleLocale(true),
		"slice", field.Array(field.String()),
		"arr", field.Array(field.Object(
			"num", field.Number(field.NumberFormatInt),
		)),
	)

	tests := []struct {
		fallback map[string]interface{}
		target   map[string]interface{}
		want     map[string]interface{}
		name     string
		wantErr  bool
	}{
		{
			name:     "Nil",
			fallback: nil,
			target:   nil,
			want:     nil,
		},
		{
			name:     "Empty",
			fallback: map[string]interface{}{},
			target:   map[string]interface{}{},
			want:     map[string]interface{}{},
		},
		{
			name: "Target Empty",
			fallback: map[string]interface{}{
				"a": "en",
				"b": 1,
				"c": "single",
				"obj1": map[string]interface{}{
					"a": "obj1_en",
					"b": 2,
					"c": 20,
					"obj2": map[string]interface{}{
						"a": 3,
						"b": "obj1_obj2_en",
						"c": "obj1_obj2_single",
					},
				},
				"obj3": map[string]interface{}{
					"a": "obj3_en",
				},
				"slice": []interface{}{"en_s1", "en_s2"},
				"arr": []interface{}{
					map[string]interface{}{"num": 11},
					map[string]interface{}{"num": 22},
				},
			},
			target: map[string]interface{}{},
			want:   map[string]interface{}{},
		},
		{
			name:     "Fallback Empty",
			fallback: map[string]interface{}{},
			target: map[string]interface{}{
				"a": "en",
				"b": 1,
				"c": "single",
				"obj1": map[string]interface{}{
					"a": "obj1_en",
					"b": 2,
					"c": 20,
					"obj2": map[string]interface{}{
						"a": 3,
						"b": "obj1_obj2_en",
						"c": "obj1_obj2_single",
					},
				},
				"obj3": map[string]interface{}{
					"a": "obj3_en",
				},
				"slice": []interface{}{"en_s1", "en_s2"},
				"arr": []interface{}{
					map[string]interface{}{"num": 11},
					map[string]interface{}{"num": 22},
				},
			},
			want: map[string]interface{}{
				"a": "en",
				"b": 1,
				"obj1": map[string]interface{}{
					"a": "obj1_en",
					"b": 2,
					"obj2": map[string]interface{}{
						"a": 3,
						"b": "obj1_obj2_en",
					},
				},
				"slice": []interface{}{"en_s1", "en_s2"},
				"arr": []interface{}{
					map[string]interface{}{"num": 11},
					map[string]interface{}{"num": 22},
				},
			},
		},
		{
			name: "Equal",
			fallback: map[string]interface{}{
				"a": "en",
				"b": 1,
				"c": "single",
				"obj1": map[string]interface{}{
					"a": "obj1_en",
					"b": 2,
					"c": 20,
					"obj2": map[string]interface{}{
						"a": 3,
						"b": "obj1_obj2_en",
						"c": "obj1_obj2_single",
					},
				},
				"obj3": map[string]interface{}{
					"a": "obj3_en",
				},
				"slice": []interface{}{"en_s1", "en_s2"},
				"arr": []interface{}{
					map[string]interface{}{"num": 11},
					map[string]interface{}{"num": 22},
				},
			},
			target: map[string]interface{}{
				"a": "en",
				"b": 1,
				"c": "single",
				"obj1": map[string]interface{}{
					"a": "obj1_en",
					"b": 2,
					"c": 20,
					"obj2": map[string]interface{}{
						"a": 3,
						"b": "obj1_obj2_en",
						"c": "obj1_obj2_single",
					},
				},
				"obj3": map[string]interface{}{
					"a": "obj3_en",
				},
				"slice": []interface{}{"en_s1", "en_s2"},
				"arr": []interface{}{
					map[string]interface{}{"num": 11},
					map[string]interface{}{"num": 22},
				},
			},
			want: map[string]interface{}{},
		},
		{
			name: "Success",
			fallback: map[string]interface{}{
				"a": "en",
				"b": 1,
				"c": "single",
				"obj1": map[string]interface{}{
					"a": "obj1_en",
					"b": 2,
					"c": 20,
					"obj2": map[string]interface{}{
						"a": 3,
						"b": "obj1_obj2_en",
						"c": "obj1_obj2_single",
					},
				},
				"obj3": map[string]interface{}{
					"a": "obj3_en",
				},
				"slice": []interface{}{"en_s1", "en_s2"},
				"arr": []interface{}{
					map[string]interface{}{"num": 11},
					map[string]interface{}{"num": 22},
				},
			},
			target: map[string]interface{}{
				"a": "ru",
				"b": 11,
				"obj1": map[string]interface{}{
					"a": "obj1_ru",
					"b": 22,
					"obj2": map[string]interface{}{
						"a": 33,
						"b": "obj1_obj2_ru",
					},
				},
				"slice": []interface{}{"ru_s3"},
				"arr": []interface{}{
					map[string]interface{}{"num": 1},
					map[string]interface{}{"num": 2},
				},
			},
			want: map[string]interface{}{
				"a": "ru",
				"b": 11,
				"obj1": map[string]interface{}{
					"a": "obj1_ru",
					"b": 22,
					"obj2": map[string]interface{}{
						"a": 33,
						"b": "obj1_obj2_ru",
					},
				},
				"slice": []interface{}{"ru_s3"},
				"arr": []interface{}{
					map[string]interface{}{"num": 1},
					map[string]interface{}{"num": 2},
				},
			},
		},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			l := &Localizer{
				schema: s,
			}
			got, err := l.extractTranslation(tt.target, tt.fallback)
			if !tt.wantErr {
				require.NoError(t, err)
				assert.Equal(t, tt.want, got)
				return
			}
			require.Error(t, err)
		})
	}
}

func TestLocalizer_getTargetAndFallBackLocales(t *testing.T) {

	tests := []struct {
		localizer    *Localizer
		wantTarget   *locales.Locale
		wantFallback *locales.Locale
		name         string
		wantErr      bool
	}{
		{
			name:      "Empty",
			localizer: &Localizer{},
			wantErr:   true,
		},
		{
			name: "Success",
			localizer: &Localizer{
				localeID: "en",
				localesKV: map[string]*locales.Locale{
					"en":      {ID: "en"},
					"default": {ID: "default"},
				},
			},
			wantTarget:   &locales.Locale{ID: "en"},
			wantFallback: &locales.Locale{ID: "default"},
		},
		{
			name: "Success fallback",
			localizer: &Localizer{
				localeID: "ru",
				localesKV: map[string]*locales.Locale{
					"en":      {ID: "en"},
					"ru":      {ID: "ru", Fallback: "en"},
					"default": {ID: "default"},
				},
			},
			wantTarget:   &locales.Locale{ID: "ru", Fallback: "en"},
			wantFallback: &locales.Locale{ID: "en"},
		},
		{
			name: "Success fallback no default",
			localizer: &Localizer{
				localeID: "ru",
				localesKV: map[string]*locales.Locale{
					"en": {ID: "en"},
					"ru": {ID: "ru", Fallback: "en"},
				},
			},
			wantTarget:   &locales.Locale{ID: "ru", Fallback: "en"},
			wantFallback: &locales.Locale{ID: "en"},
		},
		{
			name: "Success fallback no fallback",
			localizer: &Localizer{
				localeID: "ru",
				localesKV: map[string]*locales.Locale{
					"ru":      {ID: "ru", Fallback: "en"},
					"default": {ID: "default"},
				},
			},
			wantTarget:   &locales.Locale{ID: "ru", Fallback: "en"},
			wantFallback: &locales.Locale{ID: "default"},
		},
		{
			name: "Fail fallback no fallback",
			localizer: &Localizer{
				localeID: "ru",
				localesKV: map[string]*locales.Locale{
					"ru": {ID: "ru", Fallback: "en"},
				},
			},
			wantErr: true,
		},
		{
			name: "Fail target nil",
			localizer: &Localizer{
				localeID: "ru",
				localesKV: map[string]*locales.Locale{
					"ru": nil,
				},
			},
			wantErr: true,
		},
		{
			name: "Fail target disabled",
			localizer: &Localizer{
				localeID: "ru",
				localesKV: map[string]*locales.Locale{
					"ru":      {ID: "ru", Fallback: "en", Disabled: true},
					"default": {ID: "default"},
				},
			},
			wantErr: true,
		},
		{
			name: "Success fallback disabled",
			localizer: &Localizer{
				localeID: "ru",
				localesKV: map[string]*locales.Locale{
					"en":      {ID: "en", Disabled: true},
					"ru":      {ID: "ru", Fallback: "en"},
					"default": {ID: "default"},
				},
			},
			wantTarget:   &locales.Locale{ID: "ru", Fallback: "en"},
			wantFallback: &locales.Locale{ID: "default"},
		},
		{
			name: "Fail target no publish",
			localizer: &Localizer{
				localeID: "ru",
				localesKV: map[string]*locales.Locale{
					"ru":      {ID: "ru", Fallback: "en", NoPublish: true},
					"default": {ID: "default"},
				},
			},
			wantErr: true,
		},
		{
			name: "Success fallback no publish",
			localizer: &Localizer{
				localeID: "ru",
				localesKV: map[string]*locales.Locale{
					"en":      {ID: "en", NoPublish: true},
					"ru":      {ID: "ru", Fallback: "en"},
					"default": {ID: "default"},
				},
			},
			wantTarget:   &locales.Locale{ID: "ru", Fallback: "en"},
			wantFallback: &locales.Locale{ID: "en", NoPublish: true},
		},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			gotTarget, gotFallback, err := tt.localizer.getTargetAndFallBackLocales()
			if tt.wantErr {
				require.Error(t, err)
				return
			}
			require.NoError(t, err)
			assert.Equal(t, tt.wantTarget, gotTarget)
			assert.Equal(t, tt.wantFallback, gotFallback)
		})
	}
}