package inmem

import (
	"context"
	"fmt"
	"path"

	"github.com/influxdata/platform"
)

func encodeUserResourceMappingKey(resourceID, userID platform.ID) string {
	return path.Join(resourceID.String(), userID.String())
}

func (s *Service) loadUserResourceMapping(ctx context.Context, resourceID, userID platform.ID) (*platform.UserResourceMapping, error) {
	i, ok := s.userResourceMappingKV.Load(encodeUserResourceMappingKey(resourceID, userID))
	if !ok {
		return nil, fmt.Errorf("userResource mapping not found")
	}

	m, ok := i.(platform.UserResourceMapping)
	if !ok {
		return nil, fmt.Errorf("type %T is not an userResource mapping", i)
	}

	return &m, nil
}

func (s *Service) FindUserResourceBy(ctx context.Context, resourceID, userID platform.ID) (*platform.UserResourceMapping, error) {
	return s.loadUserResourceMapping(ctx, resourceID, userID)
}

func (s *Service) forEachUserResourceMapping(ctx context.Context, fn func(m *platform.UserResourceMapping) bool) error {
	var err error
	s.userResourceMappingKV.Range(func(k, v interface{}) bool {
		m, ok := v.(platform.UserResourceMapping)
		if !ok {
			err = fmt.Errorf("type %T is not a userResource mapping", v)
			return false
		}
		return fn(&m)
	})

	return err
}

func (s *Service) filterUserResourceMappings(ctx context.Context, fn func(m *platform.UserResourceMapping) bool) ([]*platform.UserResourceMapping, error) {
	mappings := []*platform.UserResourceMapping{}
	err := s.forEachUserResourceMapping(ctx, func(m *platform.UserResourceMapping) bool {
		if fn(m) {
			mappings = append(mappings, m)
		}
		return true
	})

	if err != nil {
		return nil, err
	}

	return mappings, nil
}

func (s *Service) FindUserResourceMappings(ctx context.Context, filter platform.UserResourceMappingFilter, opt ...platform.FindOptions) ([]*platform.UserResourceMapping, int, error) {
	if filter.ResourceID.Valid() && filter.UserID.Valid() {
		m, err := s.FindUserResourceBy(ctx, filter.ResourceID, filter.UserID)
		if err != nil {
			return nil, 0, err
		}
		return []*platform.UserResourceMapping{m}, 1, nil
	}

	filterFunc := func(mapping *platform.UserResourceMapping) bool {
		return (!filter.UserID.Valid() || (filter.UserID == mapping.UserID)) &&
			(!filter.ResourceID.Valid() || (filter.ResourceID == mapping.ResourceID)) &&
			(filter.UserType == "" || (filter.UserType == mapping.UserType)) &&
			(filter.ResourceType == "" || (filter.ResourceType == mapping.ResourceType))
	}

	mappings, err := s.filterUserResourceMappings(ctx, filterFunc)
	if err != nil {
		return nil, 0, err
	}

	return mappings, len(mappings), nil
}

func (s *Service) CreateUserResourceMapping(ctx context.Context, m *platform.UserResourceMapping) error {
	mapping, _ := s.FindUserResourceBy(ctx, m.ResourceID, m.UserID)
	if mapping != nil {
		return fmt.Errorf("mapping for user %s already exists", m.UserID)
	}

	s.userResourceMappingKV.Store(encodeUserResourceMappingKey(m.ResourceID, m.UserID), *m)
	return nil
}

func (s *Service) PutUserResourceMapping(ctx context.Context, m *platform.UserResourceMapping) error {
	s.userResourceMappingKV.Store(encodeUserResourceMappingKey(m.ResourceID, m.UserID), *m)
	return nil
}

func (s *Service) DeleteUserResourceMapping(ctx context.Context, resourceID, userID platform.ID) error {
	mapping, err := s.FindUserResourceBy(ctx, resourceID, userID)
	if mapping == nil && err != nil {
		return err
	}

	s.userResourceMappingKV.Delete(encodeUserResourceMappingKey(resourceID, userID))
	return nil
}

func (s *Service) deleteUserResourceMapping(ctx context.Context, filter platform.UserResourceMappingFilter) error {
	mappings, _, err := s.FindUserResourceMappings(ctx, filter)
	if mappings == nil && err != nil {
		return err
	}
	for _, m := range mappings {
		s.userResourceMappingKV.Delete(encodeUserResourceMappingKey(m.ResourceID, m.UserID))
	}

	return nil
}