package nucleus

import (
	"errors"

	"code.fbi.h-da.de/danet/gosdn/controller/event"
	eventInterfaces "code.fbi.h-da.de/danet/gosdn/controller/interfaces/event"
	"code.fbi.h-da.de/danet/gosdn/controller/interfaces/plugin"
	"code.fbi.h-da.de/danet/gosdn/controller/store"
	"github.com/google/uuid"
	hcplugin "github.com/hashicorp/go-plugin"
	log "github.com/sirupsen/logrus"
)

const (
	// PluginEventTopic is the used topic for plugin related entity changes.
	PluginEventTopic = "plugin"
)

// PluginService provides a plugin service implementation.
type PluginService struct {
	pluginStore             plugin.Store
	eventService            eventInterfaces.Service
	createPluginFromStoreFn func(plugin.LoadedPlugin) (plugin.Plugin, error)
}

// NewPluginService creates a plugin service.
func NewPluginService(pluginStore plugin.Store, eventService eventInterfaces.Service, createPluginFromStoreFn func(plugin.LoadedPlugin) (plugin.Plugin, error)) plugin.Service {
	return &PluginService{
		pluginStore:             pluginStore,
		eventService:            eventService,
		createPluginFromStoreFn: createPluginFromStoreFn,
	}
}

// Get takes a Plugin's UUID or name and returns the Plugin.
func (s *PluginService) Get(query store.Query) (plugin.Plugin, error) {
	loadedPlugin, err := s.pluginStore.Get(query)
	if err != nil {
		return nil, err
	}

	plugin, err := s.createPluginFromStore(loadedPlugin)
	if err != nil {
		return nil, err
	}

	return plugin, nil
}

// GetAll returns all stored plugins.
func (s *PluginService) GetAll() ([]plugin.Plugin, error) {
	var plugins []plugin.Plugin

	loadedPlugins, err := s.pluginStore.GetAll()
	if err != nil {
		return nil, err
	}

	for _, loadedPlugin := range loadedPlugins {
		plugin, err := s.createPluginFromStore(loadedPlugin)
		if err != nil {
			return nil, err
		}

		plugins = append(plugins, plugin)
	}

	return plugins, nil
}

// Add adds a plugin to the plugin store.
func (s *PluginService) Add(pluginToAdd plugin.Plugin) error {
	err := s.pluginStore.Add(pluginToAdd)
	if err != nil {
		return err
	}

	if err := s.eventService.PublishEvent(PluginEventTopic, event.NewAddEvent(pluginToAdd.ID())); err != nil {
		log.Error(err)
	}

	return nil
}

// Delete deletes a plugin from the plugin store.
func (s *PluginService) Delete(pluginToDelete plugin.Plugin) error {
	err := s.pluginStore.Delete(pluginToDelete)
	if err != nil {
		return err
	}

	// stop the plugin
	pluginToDelete.GetClient().Kill()

	if err := s.eventService.PublishEvent(PluginEventTopic, event.NewDeleteEvent(pluginToDelete.ID())); err != nil {
		log.Error(err)
	}

	return nil
}

func (s *PluginService) createPluginFromStore(loadedPlugin plugin.LoadedPlugin) (plugin.Plugin, error) {
	plugin, err := s.createPluginFromStoreFn(loadedPlugin)
	if err != nil {
		if errors.Is(err, hcplugin.ErrProcessNotFound) {
			plugin, err = NewPlugin(uuid.MustParse(loadedPlugin.ID))
			if err != nil {
				return nil, err
			}
			err := s.pluginStore.Update(plugin)
			if err != nil {
				return nil, err
			}
		} else {
			return nil, err
		}
	}

	return plugin, nil
}