package rbac

import (
	"code.fbi.h-da.de/danet/gosdn/controller/event"
	"code.fbi.h-da.de/danet/gosdn/controller/interfaces/rbac"
	"code.fbi.h-da.de/danet/gosdn/controller/store"
	"github.com/google/uuid"

	eventInterfaces "code.fbi.h-da.de/danet/gosdn/controller/interfaces/event"
	log "github.com/sirupsen/logrus"
)

const (
	// UserEventTopic is the used topic for user related entity changes.
	UserEventTopic = "user"
	// RoleEventTopic is the used topic for role related entity changes.
	RoleEventTopic = "role"
)

// UserService provides a user service implementation.
type UserService struct {
	userStore    rbac.UserStore
	eventService eventInterfaces.Service
}

// NewUserService creates a user service.
func NewUserService(userStore rbac.UserStore, eventService eventInterfaces.Service) rbac.UserService {
	userService := &UserService{
		userStore:    userStore,
		eventService: eventService,
	}

	return userService
}

// Add adds a user to the user store.
func (s *UserService) Add(userToAdd rbac.User) error {
	err := s.userStore.Add(userToAdd)
	if err != nil {
		return err
	}

	pubEvent := event.NewAddEvent(userToAdd.ID())
	if err := s.eventService.PublishEvent(UserEventTopic, pubEvent); err != nil {
		go func() {
			s.eventService.Reconnect()

			retryErr := s.eventService.RetryPublish(UserEventTopic, pubEvent)
			if retryErr != nil {
				log.Error(retryErr)
			}
		}()
	}

	return nil
}

// Delete deletes a user from the user store.
func (s *UserService) Delete(userToDelete rbac.User) error {
	err := s.userStore.Delete(userToDelete)
	if err != nil {
		return err
	}

	pubEvent := event.NewDeleteEvent(userToDelete.ID())
	if err := s.eventService.PublishEvent(UserEventTopic, pubEvent); err != nil {
		go func() {
			s.eventService.Reconnect()

			retryErr := s.eventService.RetryPublish(UserEventTopic, pubEvent)
			if retryErr != nil {
				log.Error(retryErr)
			}
		}()
	}

	return nil
}

// Update updates a existing user.
func (s *UserService) Update(userToUpdate rbac.User) error {
	err := s.userStore.Update(userToUpdate)
	if err != nil {
		return err
	}

	pubEvent := event.NewUpdateEvent(userToUpdate.ID())
	if err := s.eventService.PublishEvent(UserEventTopic, pubEvent); err != nil {
		go func() {
			s.eventService.Reconnect()

			retryErr := s.eventService.RetryPublish(UserEventTopic, pubEvent)
			if retryErr != nil {
				log.Error(retryErr)
			}
		}()
	}

	return nil
}

// Get takes a user's UUID or name and returns the user.
func (s *UserService) Get(query store.Query) (rbac.User, error) {
	loadedUser, err := s.userStore.Get(query)
	if err != nil {
		return nil, err
	}

	return s.createUserFromStore(loadedUser), nil
}

// GetAll returns all stored users.
func (s *UserService) GetAll() ([]rbac.User, error) {
	var users []rbac.User

	loadedUsers, err := s.userStore.GetAll()
	if err != nil {
		return nil, err
	}

	for _, loadedUser := range loadedUsers {
		users = append(users, s.createUserFromStore(loadedUser))
	}

	return users, nil
}

func (s *UserService) createUserFromStore(loadedUser rbac.LoadedUser) rbac.User {
	return NewUser(uuid.MustParse(loadedUser.ID), loadedUser.UserName, loadedUser.Roles, loadedUser.Password, loadedUser.Token, loadedUser.Salt, loadedUser.Metadata)
}

// RoleService provides a role service implementation.
type RoleService struct {
	roleStore    rbac.RoleStore
	eventService eventInterfaces.Service
}

// NewRoleService creates a role service.
func NewRoleService(roleStore rbac.RoleStore, eventService eventInterfaces.Service) rbac.RoleService {
	return &RoleService{
		roleStore:    roleStore,
		eventService: eventService,
	}
}

// Add adds a role to the role store.
func (s *RoleService) Add(roleToAdd rbac.Role) error {
	err := s.roleStore.Add(roleToAdd)
	if err != nil {
		return err
	}

	pubEvent := event.NewAddEvent(roleToAdd.ID())
	if err := s.eventService.PublishEvent(RoleEventTopic, pubEvent); err != nil {
		go func() {
			s.eventService.Reconnect()

			retryErr := s.eventService.RetryPublish(RoleEventTopic, pubEvent)
			if retryErr != nil {
				log.Error(retryErr)
			}
		}()
	}

	return nil
}

// Delete deletes a role from the role store.
func (s *RoleService) Delete(roleToDelete rbac.Role) error {
	err := s.roleStore.Delete(roleToDelete)
	if err != nil {
		return err
	}

	pubEvent := event.NewDeleteEvent(roleToDelete.ID())
	if err := s.eventService.PublishEvent(RoleEventTopic, pubEvent); err != nil {
		go func() {
			s.eventService.Reconnect()

			retryErr := s.eventService.RetryPublish(RoleEventTopic, pubEvent)
			if retryErr != nil {
				log.Error(retryErr)
			}
		}()
	}
	return nil
}

// Update updates a existing role.
func (s *RoleService) Update(roleToUpdate rbac.Role) error {
	err := s.roleStore.Update(roleToUpdate)
	if err != nil {
		return err
	}

	pubEvent := event.NewUpdateEvent(roleToUpdate.ID())
	if err := s.eventService.PublishEvent(RoleEventTopic, pubEvent); err != nil {
		go func() {
			s.eventService.Reconnect()

			retryErr := s.eventService.RetryPublish(RoleEventTopic, pubEvent)
			if retryErr != nil {
				log.Error(retryErr)
			}
		}()
	}

	return nil
}

// Get takes a roles's UUID or name and returns the role.
func (s *RoleService) Get(query store.Query) (rbac.Role, error) {
	loadedRole, err := s.roleStore.Get(query)
	if err != nil {
		return nil, err
	}

	return s.createRoleFromStore(loadedRole), nil
}

// GetAll returns all stored roles.
func (s *RoleService) GetAll() ([]rbac.Role, error) {
	var roles []rbac.Role

	loadedRoles, err := s.roleStore.GetAll()
	if err != nil {
		return nil, err
	}

	for _, loadedRole := range loadedRoles {
		roles = append(roles, s.createRoleFromStore(loadedRole))
	}

	return roles, nil
}

func (s *RoleService) createRoleFromStore(loadedRole rbac.LoadedRole) rbac.Role {
	return NewRole(uuid.MustParse(loadedRole.ID), loadedRole.RoleName, loadedRole.Description, loadedRole.Permissions)
}
