package responsemeta

import (
	"context"
	"fmt"
	"strconv"

	"google.golang.org/grpc"
	"google.golang.org/grpc/metadata"
)

// ResponseMetadataHeaderKey defines a key in the response metadata header.
type ResponseMetadataHeaderKey string

const (
	// RequestID is the key in the response header metadata for the request's tracking ID, if any.
	RequestID ResponseMetadataHeaderKey = "io.spicedb.respmeta.requestid"

	// ServerVersion is the key in the response header metadata holding the version of the server
	// handling the API request, if requested via a request header.
	ServerVersion ResponseMetadataHeaderKey = "io.spicedb.debug.version"
)

// ResponseMetadataTrailerKey defines a key in the response metadata trailer.
type ResponseMetadataTrailerKey string

const (
	// DispatchedOperationsCount is the key in the response trailer metadata for
	// the number of dispatched operations that were needed to perform the overall
	// API call.
	DispatchedOperationsCount ResponseMetadataTrailerKey = "io.spicedb.respmeta.dispatchedoperationscount"

	// CachedOperationsCount is the key in the response trailer metadata for
	// the number of *cached* operations that would have been otherwise dispatched
	// to perform the overall API call.
	CachedOperationsCount ResponseMetadataTrailerKey = "io.spicedb.respmeta.cachedoperationscount"

	// DebugInformation contains the JSON-encoded form of the debug information for the API call,
	// if requested and supported.
	//
	// NOTE: deprecated in favor of the Check response containing the trace. The JSON will now
	// contain a note indicating to look on the response object itself.
	DebugInformation ResponseMetadataTrailerKey = "io.spicedb.respmeta.debuginfo"
)

// SetResponseHeaderMetadata sets the external response metadata header on the given context.
func SetResponseHeaderMetadata(ctx context.Context, values map[ResponseMetadataHeaderKey]string) error {
	pairs := make([]string, 0, len(values)*2)
	for key, value := range values {
		pairs = append(pairs, string(key))
		pairs = append(pairs, value)
	}
	return grpc.SetHeader(ctx, metadata.Pairs(pairs...))
}

// SetResponseTrailerMetadata sets the external response metadata trailer on the given context.
func SetResponseTrailerMetadata(ctx context.Context, values map[ResponseMetadataTrailerKey]string) error {
	pairs := make([]string, 0, len(values)*2)
	for key, value := range values {
		pairs = append(pairs, string(key))
		pairs = append(pairs, value)
	}
	return grpc.SetTrailer(ctx, metadata.Pairs(pairs...))
}

// ListResponseTrailerMetadata retrieves the string value(s) for the given key in the trailer
// metadata of a SpiceDB API response.
func ListResponseTrailerMetadata(trailer metadata.MD, key ResponseMetadataTrailerKey) ([]string, error) {
	values := trailer.Get(string(key))
	if len(values) == 0 {
		return []string{}, fmt.Errorf("key `%s` not found in trailer", key)
	}

	return values, nil
}

// GetResponseTrailerMetadata retrieves a string value for the given key in the trailer
// metadata of a SpiceDB API response.
func GetResponseTrailerMetadata(trailer metadata.MD, key ResponseMetadataTrailerKey) (string, error) {
	values, err := ListResponseTrailerMetadata(trailer, key)
	if err != nil {
		return "", err
	}

	if len(values) != 1 {
		return "", fmt.Errorf("key `%s` found multiple times in trailer", key)
	}

	return values[0], nil
}

// GetResponseTrailerMetadataOrNil retrieves a string value for the given key in the trailer
// metadata of a SpiceDB API response or nil if not found.
func GetResponseTrailerMetadataOrNil(trailer metadata.MD, key ResponseMetadataTrailerKey) (*string, error) {
	values := trailer.Get(string(key))
	if len(values) == 0 {
		return nil, nil
	}

	if len(values) != 1 {
		return nil, fmt.Errorf("key `%s` found multiple times in trailer", key)
	}

	vle := values[0]
	return &vle, nil
}

// GetIntResponseTrailerMetadata retrieves an integer value for the given key in the trailer
// metadata of a SpiceDB API response.
func GetIntResponseTrailerMetadata(trailer metadata.MD, key ResponseMetadataTrailerKey) (int, error) {
	found, err := GetResponseTrailerMetadata(trailer, key)
	if err != nil {
		return 0, err
	}

	return strconv.Atoi(found)
}
