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/networkelement"
	"code.fbi.h-da.de/danet/gosdn/controller/store"
	log "github.com/sirupsen/logrus"
)

// FilesystemNetworkElementStore is the filesystem implementation of the network element store.
type FilesystemNetworkElementStore struct {
	fileMutex                sync.Mutex
	pathToNetworkElementFile string
}

// NewFilesystemNetworkElementStore returns a filesystem implementation for a pnd store.
func NewFilesystemNetworkElementStore() networkelement.Store {
	if err := store.EnsureFilesystemStorePathExists(store.NetworkElementFilenameSuffix); err != nil {
		log.Error(err)
	}
	return &FilesystemNetworkElementStore{
		pathToNetworkElementFile: store.GetCompletePathToFileStore(store.NetworkElementFilenameSuffix),
		fileMutex:                sync.Mutex{},
	}
}

func (s *FilesystemNetworkElementStore) readAllNetworkElementsFromFile() ([]networkelement.LoadedNetworkElement, error) {
	var loadedNetworkElements []networkelement.LoadedNetworkElement

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

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

	return loadedNetworkElements, nil
}

func (s *FilesystemNetworkElementStore) writeAllNetworkElementsToFile(mnes []networkelement.LoadedNetworkElement) error {
	serializedData, err := json.Marshal(mnes)
	if err != nil {
		return err
	}

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

	return nil
}

// Get takes a network element's UUID or name and returns the network element.
func (s *FilesystemNetworkElementStore) Get(query store.Query) (networkelement.LoadedNetworkElement, error) {
	s.fileMutex.Lock()
	defer s.fileMutex.Unlock()

	var lMNE networkelement.LoadedNetworkElement

	mnes, err := s.readAllNetworkElementsFromFile()
	if err != nil {
		return lMNE, err
	}

	for _, mne := range mnes {
		if mne.ID == query.ID.String() || mne.Name == query.Name {
			return mne, nil
		}
	}

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

// GetAll returns all stored network elements.
func (s *FilesystemNetworkElementStore) GetAll() ([]networkelement.LoadedNetworkElement, error) {
	s.fileMutex.Lock()
	defer s.fileMutex.Unlock()

	mnes, err := s.readAllNetworkElementsFromFile()

	return mnes, err
}

// Add adds a network element to the network element store.
func (s *FilesystemNetworkElementStore) Add(networkElementToAdd networkelement.NetworkElement) error {
	s.fileMutex.Lock()
	defer s.fileMutex.Unlock()

	mnes, err := s.readAllNetworkElementsFromFile()
	if err != nil {
		return err
	}

	var loadedNetworkElement networkelement.LoadedNetworkElement
	loadedNetworkElement, err = store.TransformObjectToLoadedObject[networkelement.NetworkElement, networkelement.LoadedNetworkElement](networkElementToAdd)
	if err != nil {
		return err
	}

	mnes = append(mnes, loadedNetworkElement)

	err = s.writeAllNetworkElementsToFile(mnes)
	if err != nil {
		return err
	}

	return nil
}

// Update updates a existing network element.
func (s *FilesystemNetworkElementStore) Update(networkElementToUpdate networkelement.NetworkElement) error {
	s.fileMutex.Lock()
	defer s.fileMutex.Unlock()

	loadedNetworkElementToUpdate, err := store.TransformObjectToLoadedObject[networkelement.NetworkElement, networkelement.LoadedNetworkElement](networkElementToUpdate)
	if err != nil {
		return err
	}

	mnes, err := s.readAllNetworkElementsFromFile()
	if err != nil {
		return err
	}

	for i, mne := range mnes {
		if mne.ID == networkElementToUpdate.ID().String() {
			mnes[i] = loadedNetworkElementToUpdate
			err = s.writeAllNetworkElementsToFile(mnes)
			if err != nil {
				return err
			}
			return nil
		}
	}

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

// Delete deletes a network element from the network element store.
func (s *FilesystemNetworkElementStore) Delete(networkElementToDelete networkelement.NetworkElement) error {
	s.fileMutex.Lock()
	defer s.fileMutex.Unlock()

	mnes, err := s.readAllNetworkElementsFromFile()
	if err != nil {
		return err
	}

	for i, mne := range mnes {
		if mne.ID == networkElementToDelete.ID().String() {
			//remove item from slice
			mnes[i] = mnes[len(mnes)-1]
			mnes = mnes[:len(mnes)-1]

			err = s.writeAllNetworkElementsToFile(mnes)
			if err != nil {
				return err
			}

			return nil
		}
	}

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