// Copyright 2022 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
////////////////////////////////////////////////////////////////////////////////

// Package monitoring defines the structs and interfaces for monitoring primitives with Tink.
// This package isn't yet production ready and might go through various changes.
package monitoring

// KeyStatus represents KeyStatusType in tink/proto/tink.proto.
type KeyStatus int

const (
	// Enabled keys can be used for cryptographic operations.
	Enabled KeyStatus = iota
	// Disabled keys can't be used, but can be re-enabled.
	Disabled
	// Destroyed keys don't exist in the keyset anymore.
	Destroyed

	// DoNotUse is intended to guard from failures that may be caused by future expansions.
	DoNotUse KeyStatus = 20
)

func (status KeyStatus) String() string {
	switch status {
	case Enabled:
		return "ENABLED"
	case Disabled:
		return "DISABLED"
	case Destroyed:
		return "DESTROYED"
	}
	return "UNKNOWN"
}

// Entry represents each entry inside a Keyset.
type Entry struct {
	Status    KeyStatus
	KeyID     uint32
	KeyType   string
	KeyPrefix string
}

// KeysetInfo represents a keyset in a certain point in time for the
// purpose of monitoring operations involving cryptographic keys.
type KeysetInfo struct {
	Annotations  map[string]string
	PrimaryKeyID uint32
	Entries      []*Entry
}

// NewKeysetInfo creates a new KeysetInfo.
func NewKeysetInfo(annotations map[string]string, primaryKeyID uint32, entries []*Entry) *KeysetInfo {
	return &KeysetInfo{
		Annotations:  annotations,
		PrimaryKeyID: primaryKeyID,
		Entries:      entries,
	}
}

// Context defines a context for monitoring events, wich includes the
// primitive and API used, and information on the keyset.
type Context struct {
	Primitive   string
	APIFunction string
	KeysetInfo  *KeysetInfo
}

// NewContext creates a new monitoring context.
func NewContext(primitive string, apiFunction string, keysetInfo *KeysetInfo) *Context {
	return &Context{
		Primitive:   primitive,
		APIFunction: apiFunction,
		KeysetInfo:  keysetInfo,
	}
}

// Logger is an interface for logging which can be created through a `Client`.
// monitoring clients are invoked by Tink during cryptographic operations to emit
// certain events.
type Logger interface {
	// Logs a successful use of `keyID` on an input of `numBytes`. Tink primitive
	// wrappers call this method when they  successfully use a key to carry out a
	// primitive method, e.g. aead.Encrypt(). As a consequence, implementations of
	// MonitoringClient should be mindful on the amount of work performed by this
	// method, as this will be called on each cryptographic operation. Implementations
	// of MonitoringClient are responsible to add context to identify, e.g., the
	// primitive and the API function.
	Log(keyID uint32, numBytes int)

	// Logs a failure. Tink calls this method when a cryptographic operation
	// failed, e.g. no key could be found to decrypt a ciphertext. In this
	// case the failure is not associated with a specific key, therefore this
	// method has no arguments. The MonitoringClient implementation is responsible
	// to add context to identify where the failure comes from.
	LogFailure()
}

// Client represents an interface to hold monitoring client context to create a `Logger`.
// A Client is registered with Tink's registry and used by primitives to obtain a `Logger`.
type Client interface {
	NewLogger(context *Context) (Logger, error)
}
