package space

import (
	"context"
	"errors"
	"fmt"

	"github.com/anyproto/any-sync/commonspace/spacestorage"
	"github.com/anyproto/any-sync/commonspace/spacesyncproto"

	"github.com/anyproto/anytype-heart/space/clientspace"
	"github.com/anyproto/anytype-heart/space/internal/spacecontroller"
	"github.com/anyproto/anytype-heart/space/internal/spaceprocess/loader"
	"github.com/anyproto/anytype-heart/space/spaceinfo"
)

type controllerWaiter struct {
	wait chan struct{}
	err  error
}

func (s *service) getCtrl(ctx context.Context, spaceId string) (ctrl spacecontroller.SpaceController, err error) {
	s.mu.Lock()
	if ctrl, ok := s.spaceControllers[spaceId]; ok {
		s.mu.Unlock()
		return ctrl, nil
	}
	if w, ok := s.waiting[spaceId]; ok {
		s.mu.Unlock()
		select {
		case <-w.wait:
		case <-ctx.Done():
			return nil, ctx.Err()
		}
		s.mu.Lock()
		err := s.waiting[spaceId].err
		if err != nil {
			s.mu.Unlock()
			return nil, err
		}
		ctrl := s.spaceControllers[spaceId]
		s.mu.Unlock()
		return ctrl, nil
	}
	s.mu.Unlock()
	return nil, ErrSpaceNotExists
}

func (s *service) startStatus(ctx context.Context, info spaceinfo.SpacePersistentInfo) (ctrl spacecontroller.SpaceController, err error) {
	s.mu.Lock()
	if ctrl, ok := s.spaceControllers[info.SpaceID]; ok {
		s.mu.Unlock()
		return ctrl, nil
	}
	if w, ok := s.waiting[info.SpaceID]; ok {
		s.mu.Unlock()
		select {
		case <-w.wait:
		case <-ctx.Done():
			return nil, ctx.Err()
		}
		s.mu.Lock()
		err := s.waiting[info.SpaceID].err
		if err != nil {
			s.mu.Unlock()
			return nil, err
		}
		ctrl := s.spaceControllers[info.SpaceID]
		s.mu.Unlock()
		return ctrl, nil
	}
	wait := make(chan struct{})
	s.waiting[info.SpaceID] = controllerWaiter{
		wait: wait,
	}
	s.mu.Unlock()
	if info.SpaceID == s.personalSpaceId {
		ctrl, err = s.factory.NewPersonalSpace(ctx, s.accountMetadataPayload)
	} else if info.EncodedKey == "" {
		ctrl, err = s.factory.NewShareableSpace(ctx, info.SpaceID, info)
	} else {
		ctrl, err = s.factory.NewStreamableSpace(ctx, info.SpaceID, info, s.accountMetadataPayload)
	}
	s.mu.Lock()
	close(wait)
	if err != nil {
		s.waiting[info.SpaceID] = controllerWaiter{
			wait: wait,
			err:  err,
		}
		s.mu.Unlock()
		return nil, err
	}
	s.spaceControllers[info.SpaceID] = ctrl
	s.mu.Unlock()
	return ctrl, nil
}

func (s *service) waitLoad(ctx context.Context, ctrl spacecontroller.SpaceController) (sp clientspace.Space, err error) {
	if ld, ok := ctrl.Current().(loader.LoadWaiter); ok {
		sp, err = ld.WaitLoad(ctx)
		if err != nil {
			err = convertSpaceError(err)
		}
		return
	}
	return nil, fmt.Errorf("failed to load space, mode is %d: %w", ctrl.Mode(), ErrFailedToLoad)
}

func convertSpaceError(err error) error {
	switch {
	case errors.Is(err, spacesyncproto.ErrSpaceIsDeleted):
		return ErrSpaceDeleted
	case errors.Is(err, spacestorage.ErrSpaceStorageMissing):
		return ErrSpaceStorageMissig
	default:
		return err
	}
}
