package plugin

import (
	"context"
	"net/rpc"

	"github.com/hashicorp/vault/logical"
)

// StorageClient is an implementation of logical.Storage that communicates
// over RPC.
type StorageClient struct {
	client *rpc.Client
}

func (s *StorageClient) List(_ context.Context, prefix string) ([]string, error) {
	var reply StorageListReply
	err := s.client.Call("Plugin.List", prefix, &reply)
	if err != nil {
		return reply.Keys, err
	}
	if reply.Error != nil {
		return reply.Keys, reply.Error
	}
	return reply.Keys, nil
}

func (s *StorageClient) Get(_ context.Context, key string) (*logical.StorageEntry, error) {
	var reply StorageGetReply
	err := s.client.Call("Plugin.Get", key, &reply)
	if err != nil {
		return nil, err
	}
	if reply.Error != nil {
		return nil, reply.Error
	}
	return reply.StorageEntry, nil
}

func (s *StorageClient) Put(_ context.Context, entry *logical.StorageEntry) error {
	var reply StoragePutReply
	err := s.client.Call("Plugin.Put", entry, &reply)
	if err != nil {
		return err
	}
	if reply.Error != nil {
		return reply.Error
	}
	return nil
}

func (s *StorageClient) Delete(_ context.Context, key string) error {
	var reply StorageDeleteReply
	err := s.client.Call("Plugin.Delete", key, &reply)
	if err != nil {
		return err
	}
	if reply.Error != nil {
		return reply.Error
	}
	return nil
}

// StorageServer is a net/rpc compatible structure for serving
type StorageServer struct {
	impl logical.Storage
}

func (s *StorageServer) List(prefix string, reply *StorageListReply) error {
	keys, err := s.impl.List(context.Background(), prefix)
	*reply = StorageListReply{
		Keys:  keys,
		Error: wrapError(err),
	}
	return nil
}

func (s *StorageServer) Get(key string, reply *StorageGetReply) error {
	storageEntry, err := s.impl.Get(context.Background(), key)
	*reply = StorageGetReply{
		StorageEntry: storageEntry,
		Error:        wrapError(err),
	}
	return nil
}

func (s *StorageServer) Put(entry *logical.StorageEntry, reply *StoragePutReply) error {
	err := s.impl.Put(context.Background(), entry)
	*reply = StoragePutReply{
		Error: wrapError(err),
	}
	return nil
}

func (s *StorageServer) Delete(key string, reply *StorageDeleteReply) error {
	err := s.impl.Delete(context.Background(), key)
	*reply = StorageDeleteReply{
		Error: wrapError(err),
	}
	return nil
}

type StorageListReply struct {
	Keys  []string
	Error error
}

type StorageGetReply struct {
	StorageEntry *logical.StorageEntry
	Error        error
}

type StoragePutReply struct {
	Error error
}

type StorageDeleteReply struct {
	Error error
}

// NOOPStorage is used to deny access to the storage interface while running a
// backend plugin in metadata mode.
type NOOPStorage struct{}

func (s *NOOPStorage) List(_ context.Context, prefix string) ([]string, error) {
	return []string{}, nil
}

func (s *NOOPStorage) Get(_ context.Context, key string) (*logical.StorageEntry, error) {
	return nil, nil
}

func (s *NOOPStorage) Put(_ context.Context, entry *logical.StorageEntry) error {
	return nil
}

func (s *NOOPStorage) Delete(_ context.Context, key string) error {
	return nil
}
