package nucleus

import (
	"encoding/json"
	"os"
	"sync"

	"code.fbi.h-da.de/danet/gosdn/controller/customerrs"
	"code.fbi.h-da.de/danet/gosdn/controller/interfaces/plugin"
	"code.fbi.h-da.de/danet/gosdn/controller/store"

	log "github.com/sirupsen/logrus"
)

// FilesystemPluginStore is used to store Plugins.
type FilesystemPluginStore struct {
	fileMutex        sync.Mutex
	pathToPluginFile string
}

// NewFilesystemPluginStore returns a filesystem implementation for a plugin store.
func NewFilesystemPluginStore() plugin.Store {
	if err := store.EnsureFilesystemStorePathExists(store.PluginFilenameSuffix); err != nil {
		log.Error(err)
	}

	return &FilesystemPluginStore{
		pathToPluginFile: store.GetCompletePathToFileStore(store.PluginFilenameSuffix),
		fileMutex:        sync.Mutex{},
	}
}

func (s *FilesystemPluginStore) readAllPluginsFromFile() ([]plugin.LoadedPlugin, error) {
	var loadedPlugins []plugin.LoadedPlugin

	content, err := os.ReadFile(s.pathToPluginFile)
	if err != nil {
		return nil, err
	}

	err = json.Unmarshal(content, &loadedPlugins)
	if err != nil {
		return nil, err
	}
	return loadedPlugins, nil
}

func (s *FilesystemPluginStore) writeAllPluginsToFile(plugins []plugin.LoadedPlugin) error {
	serializedData, err := json.Marshal(plugins)
	if err != nil {
		return err
	}

	err = os.WriteFile(s.pathToPluginFile, serializedData, 0600)
	if err != nil {
		return err
	}
	return nil
}

// Add adds a Plugin.
func (s *FilesystemPluginStore) Add(pluginToAdd plugin.Plugin) error {
	s.fileMutex.Lock()
	defer s.fileMutex.Unlock()

	plugins, err := s.readAllPluginsFromFile()
	if err != nil {
		return err
	}

	var loadedPlugin plugin.LoadedPlugin
	loadedPlugin, err = store.TransformObjectToLoadedObject[plugin.Plugin, plugin.LoadedPlugin](pluginToAdd)
	if err != nil {
		return err
	}

	plugins = append(plugins, loadedPlugin)

	err = s.writeAllPluginsToFile(plugins)
	if err != nil {
		return err
	}

	return nil
}

// Update updates an existing plugin.
func (s *FilesystemPluginStore) Update(pluginToUpdate plugin.Plugin) error {
	s.fileMutex.Lock()
	defer s.fileMutex.Unlock()

	loadedPluginToUpdate, err := store.TransformObjectToLoadedObject[plugin.Plugin, plugin.LoadedPlugin](pluginToUpdate)
	if err != nil {
		return err
	}

	plugins, err := s.readAllPluginsFromFile()
	if err != nil {
		return err
	}

	for i, plugin := range plugins {
		if plugin.ID == pluginToUpdate.ID().String() {
			plugins[i] = loadedPluginToUpdate
			err = s.writeAllPluginsToFile(plugins)
			if err != nil {
				return err
			}
			return nil
		}
	}

	return &customerrs.CouldNotUpdateError{Identifier: pluginToUpdate.ID(), Type: pluginToUpdate, Err: err}
}

// Delete deletes an Plugin.
func (s *FilesystemPluginStore) Delete(pluginToDelete plugin.Plugin) error {
	s.fileMutex.Lock()
	defer s.fileMutex.Unlock()

	plugins, err := s.readAllPluginsFromFile()
	if err != nil {
		return err
	}

	for i, plugin := range plugins {
		if plugin.ID == pluginToDelete.ID().String() {
			//remove item from slice
			plugins[i] = plugins[len(plugins)-1]
			plugins = plugins[:len(plugins)-1]

			err = s.writeAllPluginsToFile(plugins)
			if err != nil {
				return err
			}

			return nil
		}
	}
	return &customerrs.CouldNotDeleteError{Identifier: pluginToDelete.ID(), Type: pluginToDelete, Err: err}
}

// Get takes a Plugin's UUID or name and returns the Plugin. If the requested
// Plugin does not exist an error is returned.
func (s *FilesystemPluginStore) Get(query store.Query) (plugin.LoadedPlugin, error) {
	s.fileMutex.Lock()
	defer s.fileMutex.Unlock()

	var plugin plugin.LoadedPlugin

	plugins, err := s.readAllPluginsFromFile()
	if err != nil {
		return plugin, err
	}

	for _, plugin := range plugins {
		if plugin.ID == query.ID.String() {
			return plugin, nil
		}
	}

	return plugin, &customerrs.CouldNotFindError{ID: query.ID, Name: query.Name}
}

// GetAll returns all Plugins.
func (s *FilesystemPluginStore) GetAll() ([]plugin.LoadedPlugin, error) {
	s.fileMutex.Lock()
	defer s.fileMutex.Unlock()

	plugins, err := s.readAllPluginsFromFile()

	return plugins, err
}
