package setup

import (
	"context"
	"time"

	"git.perx.ru/perxis/perxis-go/pkg/clients"
	"git.perx.ru/perxis/perxis-go/pkg/collections"
	"git.perx.ru/perxis/perxis-go/pkg/content"
	"git.perx.ru/perxis/perxis-go/pkg/errors"
	"git.perx.ru/perxis/perxis-go/pkg/items"
	"git.perx.ru/perxis/perxis-go/pkg/roles"
	"git.perx.ru/perxis/perxis-go/pkg/spaces"
	"go.uber.org/zap"
)

var (
	ErrInvalidSetupConfig = errors.New("invalid setup config")
)

// Setup реализует процесс настройки пространства. Указав необходимые требования к конфигурации пространства можно
// выполнить процесс установки, проверки и удаления требований.
type Setup struct {
	SpaceID       string
	EnvironmentID string

	Roles       []RoleConfig
	Clients     []ClientConfig
	Collections []CollectionConfig
	Items       []ItemConfig

	content *content.Content

	force  bool
	remove bool

	waitForSpace bool
	attempts     uint
	delay        time.Duration

	errors []error
	logger *zap.Logger
}

func NewSetup(content *content.Content, spaceID, environmentID string, logger *zap.Logger) *Setup {
	if logger == nil {
		logger = zap.NewNop()
	}

	logger = logger.With(zap.String("Space", spaceID), zap.String("Environment", environmentID))

	return &Setup{
		SpaceID:       spaceID,
		EnvironmentID: environmentID,
		content:       content,
		logger:        logger,
	}
}

func (s *Setup) WithForce(force bool) *Setup {
	setup := *s
	setup.force = force
	return &setup
}

func (s *Setup) IsForce() bool {
	return s.force
}

func (s *Setup) WithRemove(remove bool) *Setup {
	setup := *s
	setup.remove = remove
	return &setup
}

func (s *Setup) IsRemove() bool {
	return s.remove
}

func (s *Setup) WithWaitForSpace(wait bool) *Setup {
	setup := *s
	setup.waitForSpace = wait
	if setup.attempts == 0 {
		setup.attempts = 60000
	}
	if setup.delay.Milliseconds() == 0 {
		setup.delay = 100 * time.Millisecond
	}
	return &setup
}

func (s *Setup) WaitForSpace() bool {
	return s.waitForSpace
}

func (s *Setup) SetAttempts(attempts uint) *Setup {
	setup := *s
	setup.attempts = attempts
	return &setup
}

func (s *Setup) SetDelay(delay time.Duration) *Setup {
	setup := *s
	setup.delay = delay
	return &setup
}

func (s *Setup) HasErrors() bool {
	return len(s.errors) > 0
}

func (s *Setup) AddError(err error) {
	s.errors = append(s.errors, err)
}

func (s *Setup) Errors() []error {
	return s.errors
}

func (s *Setup) Error() error {
	return errors.WithErrors(ErrInvalidSetupConfig, s.errors...)
}

// AddRoles добавляет требования к настройке ролей в пространстве
func (s *Setup) AddRoles(roles []*roles.Role, opt ...RolesOption) *Setup {
	for _, role := range roles {
		s.AddRole(role, opt...)
	}
	return s
}

func (s *Setup) AddRole(role *roles.Role, opt ...RolesOption) *Setup {
	s.Roles = append(s.Roles, NewRoleConfig(role, opt...))
	return s
}

// AddClients добавляет требования к настройке приложений в пространстве
func (s *Setup) AddClients(clients []*clients.Client, opt ...ClientsOption) *Setup {
	for _, client := range clients {
		s.AddClient(client, opt...)
	}
	return s
}

func (s *Setup) AddClient(client *clients.Client, opt ...ClientsOption) *Setup {
	s.Clients = append(s.Clients, NewClientConfig(client, opt...))
	return s
}

// AddCollections добавляет требования к настройке коллекций в пространстве
func (s *Setup) AddCollections(collections []*collections.Collection, opt ...CollectionsOption) *Setup {
	for _, col := range collections {
		s.AddCollection(col, opt...)
	}
	return s
}

func (s *Setup) AddCollection(collection *collections.Collection, opt ...CollectionsOption) *Setup {
	config, err := NewCollectionConfig(collection, opt...)
	if err != nil {
		s.AddError(err)
		return s
	}
	s.Collections = append(s.Collections, config)
	return s
}

// AddItems добавляет требования к настройке элементов в пространстве
func (s *Setup) AddItems(items []*items.Item, opt ...ItemsOption) *Setup {
	for _, item := range items {
		s.AddItem(item, opt...)
	}
	return s
}

func (s *Setup) AddItem(item *items.Item, opt ...ItemsOption) *Setup {
	s.Items = append(s.Items, NewItemConfig(item, opt...))
	return s
}

// Install выполняет установку необходимых требований
func (s *Setup) Install(ctx context.Context) error {
	var err error
	if s.waitForSpace {
		err = spaces.CheckIsSpaceAvailableWithRetry(ctx, s.content.Spaces, s.SpaceID, s.delay, s.attempts, s.logger)
	}
	if err == nil {
		if err := s.InstallRoles(ctx); err != nil {
			return err
		}
		if err := s.InstallClients(ctx); err != nil {
			return err
		}
		if err := s.InstallCollections(ctx); err != nil {
			return err
		}
		if err := s.InstallItems(ctx); err != nil {
			return err
		}
	}
	return err
}

// Check выполняет проверку требований
func (s *Setup) Check(ctx context.Context) error {
	var err error
	if s.waitForSpace {
		err = spaces.CheckIsReadAvailableWithRetry(ctx, s.content.Spaces, s.SpaceID, s.delay, s.attempts, s.logger)
	}
	if err == nil {
		if err := s.CheckRoles(ctx); err != nil {
			return err
		}
		if err := s.CheckClients(ctx); err != nil {
			return err
		}
		if err := s.CheckCollections(ctx); err != nil {
			return err
		}
		if err := s.CheckItems(ctx); err != nil {
			return err
		}
		return nil
	}
	return err

}

// Uninstall выполняет удаление установленных раннее требований
func (s *Setup) Uninstall(ctx context.Context) error {
	var err error
	if s.waitForSpace {
		err = spaces.CheckIsSpaceAvailableWithRetry(ctx, s.content.Spaces, s.SpaceID, s.delay, s.attempts, s.logger)
	}
	if err == nil {
		// В случае если необходимо удалить данные удаляем все что создано при установке расширения
		if err := s.UninstallClients(ctx); err != nil {
			return err
		}
		if err := s.UninstallRoles(ctx); err != nil {
			return err
		}
		if err := s.UninstallCollections(ctx); err != nil {
			return err
		}
		if err := s.UninstallItems(ctx); err != nil {
			return err
		}
		s.logger.Info("Uninstall finished", zap.String(s.SpaceID, "spaceID"))
		return nil
	}
	return err
}