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"
)

// FileSystemRoleStore is the filesystem implementation of the role store.
type FileSystemRoleStore struct {
	fileMutex      sync.Mutex
	pathToRoleFile string
}

// NewFileSystemRoleStore returns a filesystem implementation for a role store.
func NewFileSystemRoleStore() rbac.RoleStore {
	if err := store.EnsureFilesystemStorePathExists(store.RoleFilename); err != nil {
		log.Error(err)
	}

	return &FileSystemRoleStore{
		fileMutex:      sync.Mutex{},
		pathToRoleFile: store.GetCompletePathToFileStore(store.RoleFilename),
	}
}

func (s *FileSystemRoleStore) readAllRolesFromFile() ([]rbac.LoadedRole, error) {
	var loadedRoles []rbac.LoadedRole
	content, err := os.ReadFile(s.pathToRoleFile)
	if err != nil {
		return nil, err
	}

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

	return loadedRoles, nil
}

func (s *FileSystemRoleStore) writeAllRolesToFile(roles []rbac.LoadedRole) error {
	serializedData, err := json.Marshal(roles)
	if err != nil {
		return err
	}

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

	return nil
}

// Add adds a Role to the Role store.
func (s *FileSystemRoleStore) Add(roleToAdd rbac.Role) error {
	s.fileMutex.Lock()
	defer s.fileMutex.Unlock()

	roles, err := s.readAllRolesFromFile()
	if err != nil {
		return err
	}

	var loadedRole rbac.LoadedRole
	loadedRole, err = store.TransformObjectToLoadedObject[rbac.Role, rbac.LoadedRole](roleToAdd)
	if err != nil {
		return err
	}

	roles = append(roles, loadedRole)

	err = s.writeAllRolesToFile(roles)
	if err != nil {
		return err
	}

	return nil
}

// Delete deletes a Role from the Role store.
func (s *FileSystemRoleStore) Delete(roleToDelete rbac.Role) error {
	s.fileMutex.Lock()
	defer s.fileMutex.Unlock()

	roles, err := s.readAllRolesFromFile()
	if err != nil {
		return err
	}

	for i, role := range roles {
		if role.ID == roleToDelete.ID().String() {
			//remove item from slice
			roles[i] = roles[len(roles)-1]
			roles = roles[:len(roles)-1]

			err = s.writeAllRolesToFile(roles)
			if err != nil {
				return err
			}

			return nil
		}
	}

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

// Get takes a Roles ID and return the Role if found.
func (s *FileSystemRoleStore) Get(query store.Query) (rbac.LoadedRole, error) {
	s.fileMutex.Lock()
	defer s.fileMutex.Unlock()

	var role rbac.LoadedRole
	roles, err := s.readAllRolesFromFile()
	if err != nil {
		return role, err
	}

	for _, role := range roles {
		if role.ID == query.ID.String() || role.RoleName == query.Name {
			return role, nil
		}
	}

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

// GetAll returns all the Roles.
func (s *FileSystemRoleStore) GetAll() ([]rbac.LoadedRole, error) {
	s.fileMutex.Lock()
	defer s.fileMutex.Unlock()

	Roles, err := s.readAllRolesFromFile()
	return Roles, err
}

// Update updates an exsisting Role.
func (s *FileSystemRoleStore) Update(roleToUpdate rbac.Role) error {
	s.fileMutex.Lock()
	defer s.fileMutex.Unlock()

	loadedRole, err := store.TransformObjectToLoadedObject[rbac.Role, rbac.LoadedRole](roleToUpdate)
	if err != nil {
		return err
	}
	Roles, err := s.readAllRolesFromFile()
	if err != nil {
		return err
	}

	for i, Role := range Roles {
		if Role.ID == roleToUpdate.ID().String() {
			Roles[i] = loadedRole
			err = s.writeAllRolesToFile(Roles)
			if err != nil {
				return err
			}
			return nil
		}
	}

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