package setup

import (
	"context"
	"testing"

	"git.perx.ru/perxis/perxis-go/pkg/collections"
	mockscollections "git.perx.ru/perxis/perxis-go/pkg/collections/mocks"
	"git.perx.ru/perxis/perxis-go/pkg/content"
	"git.perx.ru/perxis/perxis-go/pkg/environments"
	envmocks "git.perx.ru/perxis/perxis-go/pkg/environments/mocks"
	"git.perx.ru/perxis/perxis-go/pkg/errors"
	"git.perx.ru/perxis/perxis-go/pkg/extension"
	"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/mock"
)

func TestSetup_InstallCollections(t *testing.T) {
	tests := []struct {
		name            string
		collections     []*collections.Collection
		collectionsCall func(svc *mockscollections.Collections)
		envsCall        func(svc *envmocks.Environments)
		force           bool
		wantErr         func(t *testing.T, err error)
	}{
		{
			name:            "Nil collections",
			collections:     nil,
			collectionsCall: func(svc *mockscollections.Collections) {},
			wantErr: func(t *testing.T, err error) {
				assert.NoError(t, err)
			},
		},
		{
			name:        "Install one collection success",
			collections: []*collections.Collection{{ID: "1", SpaceID: "sp", Name: "space", EnvID: "env", Schema: schema.New("name", field.String())}},
			collectionsCall: func(svc *mockscollections.Collections) {
				svc.On("Get", mock.Anything, "sp", "env", "1").Return(nil, errors.New("not found")).Once()
				svc.On("Create", mock.Anything, &collections.Collection{ID: "1", SpaceID: "sp", Name: "space", EnvID: "env", Schema: schema.New("name", field.String())}).Return(&collections.Collection{ID: "1", SpaceID: "sp", Name: "space", EnvID: "env", Schema: schema.New("name", field.String())}, nil).Once()
				svc.On("SetSchema", mock.Anything, "sp", "env", "1", schema.New("name", field.String())).Return(nil).Once()
			},
			envsCall: func(svc *envmocks.Environments) {
				svc.On("Migrate", mock.Anything, "sp", "env", &environments.MigrateOptions{Wait: true}).Return(nil).Once()
			},
			wantErr: func(t *testing.T, err error) {
				assert.NoError(t, err)
			},
		},
		{
			name:        "Install one collection fails",
			collections: []*collections.Collection{{ID: "1", SpaceID: "sp", Name: "space", EnvID: "env", Schema: schema.New("name", field.String())}},
			collectionsCall: func(svc *mockscollections.Collections) {
				svc.On("Get", mock.Anything, "sp", "env", "1").Return(nil, errors.New("not found")).Once()
				svc.On("Create", mock.Anything, &collections.Collection{ID: "1", SpaceID: "sp", Name: "space", EnvID: "env", Schema: schema.New("name", field.String())}).Return(nil, errors.New("some error")).Once()
			},
			wantErr: func(t *testing.T, err error) {
				assert.Error(t, err)
				assert.EqualError(t, err, "failed to install collection: some error")
				assert.Contains(t, errors.GetDetail(err), "Возникла ошибка при настройке коллекции space(1)")
			},
		},
		{
			name:        "Install multiple collections success",
			collections: []*collections.Collection{{ID: "1", SpaceID: "sp", Name: "space", EnvID: "env", Schema: schema.New("name", field.String())}, {ID: "2", SpaceID: "sp", Name: "space", EnvID: "env", Schema: schema.New("name", field.String())}},
			collectionsCall: func(svc *mockscollections.Collections) {
				svc.On("Get", mock.Anything, "sp", "env", "1").Return(nil, errors.New("not found")).Once()
				svc.On("Get", mock.Anything, "sp", "env", "2").Return(nil, errors.New("not found")).Once()
				svc.On("Create", mock.Anything, &collections.Collection{ID: "1", SpaceID: "sp", Name: "space", EnvID: "env", Schema: schema.New("name", field.String())}).Return(&collections.Collection{ID: "1", SpaceID: "sp", Name: "space", EnvID: "env", Schema: schema.New("name", field.String())}, nil).Once()
				svc.On("Create", mock.Anything, &collections.Collection{ID: "2", SpaceID: "sp", Name: "space", EnvID: "env", Schema: schema.New("name", field.String())}).Return(&collections.Collection{ID: "2", SpaceID: "sp", Name: "space", EnvID: "env", Schema: schema.New("name", field.String())}, nil).Once()
				svc.On("SetSchema", mock.Anything, "sp", "env", "1", schema.New("name", field.String())).Return(nil).Once()
				svc.On("SetSchema", mock.Anything, "sp", "env", "2", schema.New("name", field.String())).Return(nil).Once()
			},
			envsCall: func(svc *envmocks.Environments) {
				svc.On("Migrate", mock.Anything, "sp", "env", &environments.MigrateOptions{Wait: true}).Return(nil).Twice()
			},
			wantErr: func(t *testing.T, err error) {
				assert.NoError(t, err)
			},
		},
		{
			name:        "Install multiple collections fails",
			collections: []*collections.Collection{{ID: "1", SpaceID: "sp", Name: "space", EnvID: "env", Schema: schema.New("name", field.String())}, {ID: "2", SpaceID: "sp", Name: "space", EnvID: "env", Schema: schema.New("name", field.String())}},
			collectionsCall: func(svc *mockscollections.Collections) {
				svc.On("Get", mock.Anything, "sp", "env", "1").Return(nil, errors.New("not found")).Once()
				svc.On("Create", mock.Anything, &collections.Collection{ID: "1", SpaceID: "sp", Name: "space", EnvID: "env", Schema: schema.New("name", field.String())}).Return(nil, errors.New("some error")).Once()
			},
			wantErr: func(t *testing.T, err error) {
				assert.Error(t, err)
				assert.EqualError(t, err, "failed to install collection: some error")
				assert.Contains(t, errors.GetDetail(err), "Возникла ошибка при настройке коллекции space(1)")
			},
		},
		{
			name:        "Update extension collection with metadata",
			collections: []*collections.Collection{{ID: "1", SpaceID: "sp", EnvID: "env", Schema: schema.New("name", field.String()).WithMetadata(extension.ExtensionMetadataKey, "test-extension")}},
			collectionsCall: func(svc *mockscollections.Collections) {
				svc.On("Get", mock.Anything, "sp", "env", "1").Return(&collections.Collection{ID: "1", SpaceID: "sp", EnvID: "env", Schema: schema.New("name", field.String()).WithMetadata(extension.ExtensionMetadataKey, "test-extension")}, nil).Once()
				svc.On("Update", mock.Anything, &collections.Collection{ID: "1", SpaceID: "sp", EnvID: "env", Schema: schema.New("name", field.String()).WithMetadata(extension.ExtensionMetadataKey, "test-extension")}).Return(nil).Once()
				svc.On("SetSchema", mock.Anything, "sp", "env", "1", schema.New("name", field.String()).WithMetadata(extension.ExtensionMetadataKey, "test-extension")).Return(nil).Once()
			},
			envsCall: func(svc *envmocks.Environments) {
				svc.On("Migrate", mock.Anything, "sp", "env", &environments.MigrateOptions{Wait: true}).Return(nil).Once()
			},
			wantErr: func(t *testing.T, err error) {
				assert.NoError(t, err)
			},
		},
		//todo после удаления флага alwaysSetSchema данный тест будет работать, сейчас он будет всегда падать, поэтому пока закомментирован
		//{
		//name:        "Fail to update user collection with same id as in extensions collection",
		//collections: []*collections.Collection{{ID: "1", SpaceID: "sp", EnvID: "env", Schema: schema.New("name", field.String()).WithMetadata(extension.ExtensionMetadataKey, "test-extension")}},
		//collectionsCall: func(svc *mockscollections.Collections) {
		//	svc.On("Get", mock.Anything, "sp", "env", "1").Return(&collections.Collection{ID: "1", SpaceID: "sp", EnvID: "env", Schema: schema.New("name", field.String())}, nil).Once()
		//},
		//wantErr: func(t *testing.T, err error) {
		//	assert.Error(t, err)
		//	assert.ErrorIs(t, err, collections.ErrAlreadyExists)
		//},
		//},
		{
			name:        "Update user collection with same id as in extensions collection with force",
			collections: []*collections.Collection{{ID: "1", SpaceID: "sp", EnvID: "env", Schema: schema.New("name", field.String()).WithMetadata(extension.ExtensionMetadataKey, "test-extension")}},
			collectionsCall: func(svc *mockscollections.Collections) {
				svc.On("Get", mock.Anything, "sp", "env", "1").Return(&collections.Collection{ID: "1", SpaceID: "sp", EnvID: "env", Schema: schema.New("name", field.String())}, nil).Once()
				svc.On("Update", mock.Anything, &collections.Collection{ID: "1", SpaceID: "sp", EnvID: "env", Schema: schema.New("name", field.String()).WithMetadata(extension.ExtensionMetadataKey, "test-extension")}).Return(nil).Once()
				svc.On("SetSchema", mock.Anything, "sp", "env", "1", schema.New("name", field.String()).WithMetadata(extension.ExtensionMetadataKey, "test-extension")).Return(nil).Once()
			},
			envsCall: func(svc *envmocks.Environments) {
				svc.On("Migrate", mock.Anything, "sp", "env", &environments.MigrateOptions{Wait: true}).Return(nil).Once()
			},
			wantErr: func(t *testing.T, err error) {
				assert.NoError(t, err)
			},
			force: true,
		},
		{
			name:        "Update exist view collection with the same id",
			collections: []*collections.Collection{{ID: "1", SpaceID: "sp", EnvID: "env", Schema: schema.New("name", field.String()).WithMetadata(extension.ExtensionMetadataKey, "test-extension")}},
			collectionsCall: func(svc *mockscollections.Collections) {
				svc.On("Get", mock.Anything, "sp", "env", "1").Return(&collections.Collection{ID: "1", SpaceID: "sp", EnvID: "env", View: &collections.View{SpaceID: "sp2", EnvID: environments.DefaultEnvironment, CollectionID: "2"}}, nil).Once()
				svc.On("Update", mock.Anything, &collections.Collection{ID: "1", SpaceID: "sp", EnvID: "env", Schema: schema.New("name", field.String()).WithMetadata(extension.ExtensionMetadataKey, "test-extension")}).Return(nil).Once()
				svc.On("SetSchema", mock.Anything, "sp", "env", "1", schema.New("name", field.String()).WithMetadata(extension.ExtensionMetadataKey, "test-extension")).Return(nil).Once()
			},
			envsCall: func(svc *envmocks.Environments) {
				svc.On("Migrate", mock.Anything, "sp", "env", &environments.MigrateOptions{Wait: true}).Return(nil).Once()
			},
			wantErr: func(t *testing.T, err error) {
				assert.NoError(t, err)
			},
		},
		//todo после удаления флага alwaysSetSchema данный тест будет работать, сейчас он будет всегда падать, поэтому пока закомментирован
		//{
		//	name:        "Update view collection with the same id to new view collection",
		//	collections: []*collections.Collection{{ID: "1", SpaceID: "sp", EnvID: "env", View: &collections.View{SpaceID: "sp3", EnvID: environments.DefaultEnvironment, CollectionID: "3"}}},
		//	collectionsCall: func(svc *mockscollections.Collections) {
		//		svc.On("Get", mock.Anything, "sp", "env", "1").Return(&collections.Collection{ID: "1", SpaceID: "sp", EnvID: "env", View: &collections.View{SpaceID: "sp2", EnvID: environments.DefaultEnvironment, CollectionID: "2"}}, nil).Once()
		//		svc.On("Update", mock.Anything, &collections.Collection{ID: "1", SpaceID: "sp", EnvID: "env", View: &collections.View{SpaceID: "sp3", EnvID: environments.DefaultEnvironment, CollectionID: "3"}}).Return(nil).Once()
		//	},
		//	envsCall: func(svc *envmocks.Environments) {
		//		svc.On("Migrate", mock.Anything, "sp", "env", &environments.MigrateOptions{Wait: true}).Return(nil).Once()
		//	},
		//	wantErr: func(t *testing.T, err error) {
		//		assert.NoError(t, err)
		//	},
		//},
		{
			name:        "Fail to install collection on migrate",
			collections: []*collections.Collection{{ID: "1", SpaceID: "sp", Name: "space", EnvID: "env", Schema: schema.New("name", field.String())}},
			collectionsCall: func(svc *mockscollections.Collections) {
				svc.On("Get", mock.Anything, "sp", "env", "1").Return(nil, errors.New("not found")).Once()
				svc.On("Create", mock.Anything, &collections.Collection{ID: "1", SpaceID: "sp", Name: "space", EnvID: "env", Schema: schema.New("name", field.String())}).Return(&collections.Collection{ID: "1", SpaceID: "sp", Name: "space", EnvID: "env", Schema: schema.New("name", field.String())}, nil).Once()
				svc.On("SetSchema", mock.Anything, "sp", "env", "1", schema.New("name", field.String())).Return(nil).Once()
			},
			envsCall: func(svc *envmocks.Environments) {
				svc.On("Migrate", mock.Anything, "sp", "env", &environments.MigrateOptions{Wait: true}).Return(errors.New("migrate error")).Once()
			},
			wantErr: func(t *testing.T, err error) {
				assert.Error(t, err)
				assert.EqualError(t, err, "migrate: migrate error")
				assert.Contains(t, errors.GetDetail(err), "Возникла ошибка при миграции данных")
			},
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			c := &mockscollections.Collections{}
			e := &envmocks.Environments{}
			if tt.collectionsCall != nil {
				tt.collectionsCall(c)
			}
			if tt.envsCall != nil {
				tt.envsCall(e)
			}

			s := NewSetup(&content.Content{Collections: c, Environments: e}, "sp", "env", nil).WithForce(tt.force)
			s.AddCollections(tt.collections)
			tt.wantErr(t, s.InstallCollections(context.Background()))
		})
	}
}

func Test_isCollsSchemaMetadataEqual(t *testing.T) {
	type args struct {
	}
	tests := []struct {
		name string
		s1   *schema.Schema
		s2   *schema.Schema
		want bool
	}{
		{
			"Not equal #1 (no metadata in exist coll)",
			schema.New("name", field.String()).WithMetadata(extension.ExtensionMetadataKey, "test"),
			schema.New("name", field.String()),
			false,
		},
		{
			"Not equal #2 (different metadata)",
			schema.New("name", field.String()).WithMetadata(extension.ExtensionMetadataKey, "test"),
			schema.New("name", field.String()).WithMetadata("test", "test"),
			false,
		},
		{
			"Equal #1 (no metadata)",
			schema.New("name", field.String()),
			schema.New("name", field.String()),
			true,
		},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			assert.Equalf(t, tt.want, isCollsSchemaMetadataEqual(tt.s1, tt.s2), "isCollsSchemaMetadataEqual(%v, %v)", tt.s1, tt.s2)
		})
	}
}