package rbac

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

	"code.fbi.h-da.de/danet/gosdn/controller/customerrs"
	"code.fbi.h-da.de/danet/gosdn/controller/interfaces/rbac"
	"code.fbi.h-da.de/danet/gosdn/controller/store"
	log "github.com/sirupsen/logrus"
)

// FileSystemUserStore is the filesystem implementation of the user store.
type FileSystemUserStore struct {
	fileMutex      sync.Mutex
	pathToUserFile string
}

// NewFileSystemUserStore returns a filesystem implementation for a user store.
func NewFileSystemUserStore() rbac.UserStore {
	if err := store.EnsureFilesystemStorePathExists(store.UserFilename); err != nil {
		log.Error(err)
	}

	return &FileSystemUserStore{
		fileMutex:      sync.Mutex{},
		pathToUserFile: store.GetCompletePathToFileStore(store.UserFilename),
	}
}

func (s *FileSystemUserStore) readAllUsersFromFile() ([]rbac.LoadedUser, error) {
	var loadedUsers []rbac.LoadedUser
	content, err := os.ReadFile(s.pathToUserFile)
	if err != nil {
		return nil, err
	}

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

	return loadedUsers, nil
}

func (s *FileSystemUserStore) writeAllUsersToFile(users []rbac.LoadedUser) error {
	serializedData, err := json.Marshal(users)
	if err != nil {
		return err
	}

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

	return nil
}

// Add adds a User to the User store.
func (s *FileSystemUserStore) Add(UserToAdd rbac.User) error {
	s.fileMutex.Lock()
	defer s.fileMutex.Unlock()

	users, err := s.readAllUsersFromFile()
	if err != nil {
		return err
	}

	var loadedUser rbac.LoadedUser
	loadedUser, err = store.TransformObjectToLoadedObject[rbac.User, rbac.LoadedUser](UserToAdd)
	if err != nil {
		return err
	}

	users = append(users, loadedUser)

	err = s.writeAllUsersToFile(users)
	if err != nil {
		return err
	}

	return nil
}

// Delete deletes a User from the User store.
func (s *FileSystemUserStore) Delete(userToDelete rbac.User) error {
	s.fileMutex.Lock()
	defer s.fileMutex.Unlock()

	users, err := s.readAllUsersFromFile()
	if err != nil {
		return err
	}

	for i, user := range users {
		if user.ID == userToDelete.ID().String() {
			//remove item from slice
			users[i] = users[len(users)-1]
			users = users[:len(users)-1]

			err = s.writeAllUsersToFile(users)
			if err != nil {
				return err
			}

			return nil
		}
	}

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

// Get takes a Users ID and return the User if found.
func (s *FileSystemUserStore) Get(query store.Query) (rbac.LoadedUser, error) {
	s.fileMutex.Lock()
	defer s.fileMutex.Unlock()

	var user rbac.LoadedUser

	users, err := s.readAllUsersFromFile()
	if err != nil {
		return user, err
	}

	for _, user := range users {
		if user.ID == query.ID.String() || user.UserName == query.Name {
			return user, nil
		}
	}
	return user, &customerrs.CouldNotFindError{ID: query.ID, Name: query.Name}
}

// GetAll returns all the Users.
func (s *FileSystemUserStore) GetAll() ([]rbac.LoadedUser, error) {
	s.fileMutex.Lock()
	defer s.fileMutex.Unlock()

	Users, err := s.readAllUsersFromFile()
	return Users, err
}

// Update updates an exsisting user.
func (s *FileSystemUserStore) Update(userToUpdate rbac.User) error {
	s.fileMutex.Lock()
	defer s.fileMutex.Unlock()

	loadedUser, err := store.TransformObjectToLoadedObject[rbac.User, rbac.LoadedUser](userToUpdate)
	if err != nil {
		return err
	}
	users, err := s.readAllUsersFromFile()
	if err != nil {
		return err
	}

	for i, user := range users {
		if user.ID == userToUpdate.ID().String() {
			users[i] = loadedUser
			err = s.writeAllUsersToFile(users)
			if err != nil {
				return err
			}
			return nil
		}
	}

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