// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0

package logical

import (
	"context"

	log "github.com/hashicorp/go-hclog"
)

// BackendType is the type of backend that is being implemented
type BackendType uint32

// The these are the types of backends that can be derived from
// logical.Backend
const (
	TypeUnknown    BackendType = 0 // This is also the zero-value for BackendType
	TypeLogical    BackendType = 1
	TypeCredential BackendType = 2
)

// Stringer implementation
func (b BackendType) String() string {
	switch b {
	case TypeLogical:
		return "secret"
	case TypeCredential:
		return "auth"
	}

	return "unknown"
}

// Backend interface must be implemented to be "mountable" at
// a given path. Requests flow through a router which has various mount
// points that flow to a logical backend. The logic of each backend is flexible,
// and this is what allows materialized keys to function. There can be specialized
// logical backends for various upstreams (Consul, PostgreSQL, MySQL, etc) that can
// interact with remote APIs to generate keys dynamically. This interface also
// allows for a "procfs" like interaction, as internal state can be exposed by
// acting like a logical backend and being mounted.
type Backend interface {
	// Initialize is used to initialize a plugin after it has been mounted.
	Initialize(context.Context, *InitializationRequest) error

	// HandleRequest is used to handle a request and generate a response.
	// The backends must check the operation type and handle appropriately.
	HandleRequest(context.Context, *Request) (*Response, error)

	// SpecialPaths is a list of paths that are special in some way.
	// See PathType for the types of special paths. The key is the type
	// of the special path, and the value is a list of paths for this type.
	// This is not a regular expression but is an exact match. If the path
	// ends in '*' then it is a prefix-based match. The '*' can only appear
	// at the end.
	SpecialPaths() *Paths

	// System provides an interface to access certain system configuration
	// information, such as globally configured default and max lease TTLs.
	System() SystemView

	// Logger provides an interface to access the underlying logger. This
	// is useful when a struct embeds a Backend-implemented struct that
	// contains a private instance of logger.
	Logger() log.Logger

	// HandleExistenceCheck is used to handle a request and generate a response
	// indicating whether the given path exists or not; this is used to
	// understand whether the request must have a Create or Update capability
	// ACL applied. The first bool indicates whether an existence check
	// function was found for the backend; the second indicates whether, if an
	// existence check function was found, the item exists or not.
	HandleExistenceCheck(context.Context, *Request) (bool, bool, error)

	// Cleanup is invoked during an unmount of a backend to allow it to
	// handle any cleanup like connection closing or releasing of file handles.
	Cleanup(context.Context)

	// InvalidateKey may be invoked when an object is modified that belongs
	// to the backend. The backend can use this to clear any caches or reset
	// internal state as needed.
	InvalidateKey(context.Context, string)

	// Setup is used to set up the backend based on the provided backend
	// configuration.
	Setup(context.Context, *BackendConfig) error

	// Type returns the BackendType for the particular backend
	Type() BackendType
}

// BackendConfig is provided to the factory to initialize the backend
type BackendConfig struct {
	// View should not be stored, and should only be used for initialization
	StorageView Storage

	// The backend should use this logger. The log should not contain any secrets.
	Logger log.Logger

	// System provides a view into a subset of safe system information that
	// is useful for backends, such as the default/max lease TTLs
	System SystemView

	// BackendUUID is a unique identifier provided to this backend. It's useful
	// when a backend needs a consistent and unique string without using storage.
	BackendUUID string

	// Config is the opaque user configuration provided when mounting
	Config map[string]string

	// EventsSender provides a mechanism to interact with Vault events.
	EventsSender EventSender
}

// Factory is the factory function to create a logical backend.
type Factory func(context.Context, *BackendConfig) (Backend, error)

// Paths is the structure of special paths that is used for SpecialPaths.
type Paths struct {
	// Root are the API paths that require a root token to access
	Root []string

	// Unauthenticated are the API paths that can be accessed without any auth.
	// These can't be regular expressions, it is either exact match, a prefix
	// match and/or a wildcard match. For prefix match, append '*' as a suffix.
	// For a wildcard match, use '+' in the segment to match any identifier
	// (e.g. 'foo/+/bar'). Note that '+' can't be adjacent to a non-slash.
	Unauthenticated []string

	// LocalStorage are storage paths (prefixes) that are local to this cluster;
	// this indicates that these paths should not be replicated across performance clusters
	// (DR replication is unaffected).
	LocalStorage []string

	// SealWrapStorage are storage paths that, when using a capable seal,
	// should be seal wrapped with extra encryption. It is exact matching
	// unless it ends with '/' in which case it will be treated as a prefix.
	SealWrapStorage []string

	// WriteForwardedStorage are storage paths that, when running on a PR
	// Secondary cluster, cause a GRPC call up to the PR Primary cluster's
	// active node to handle storage.Put(...) and storage.Delete(...) events.
	// These paths MUST include a {{clusterId}} literal, which the write layer
	// will resolve to this cluster's UUID ("replication set" identifier).
	// storage.List(...) and storage.Get(...) operations occur from the
	// locally replicated data set, but can use path template expansion to be
	// identifier agnostic.
	//
	// These paths require careful considerations by developers to use. In
	// particular, writes on secondary clusters will not appear (when a
	// corresponding read is issued immediately after a write) until the
	// replication from primary->secondary has occurred. This replication
	// triggers an InvalidateKey(...) call on the secondary, which can be
	// used to detect the write has finished syncing. However, this will
	// likely occur after the request has finished, so it is important to
	// not block on this occurring.
	//
	// On standby nodes, like all storage write operations, this will trigger
	// an ErrReadOnly return.
	WriteForwardedStorage []string
}

type Auditor interface {
	AuditRequest(ctx context.Context, input *LogInput) error
	AuditResponse(ctx context.Context, input *LogInput) error
}

type PluginVersion struct {
	Version string
}

// PluginVersioner is an optional interface to return version info.
type PluginVersioner interface {
	// PluginVersion returns the version for the backend
	PluginVersion() PluginVersion
}

var EmptyPluginVersion = PluginVersion{""}
