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())).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 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)
			},
		},
		{
			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)
			},
		},
	}

	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()))
		})
	}
}