/*
 * Copyright 2019 The Knative Authors
 *
 * 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 broker

import (
	"context"

	cloudevents "github.com/cloudevents/sdk-go/v1"
	"github.com/cloudevents/sdk-go/v1/cloudevents/client"
	cetypes "github.com/cloudevents/sdk-go/v1/cloudevents/types"
	"go.uber.org/zap"
)

const (
	// TTLAttribute is the name of the CloudEvents extension attribute used to store the
	// Broker's TTL (number of times a single event can reply through a Broker continuously). All
	// interactions with the attribute should be done through the GetTTL and SetTTL functions.
	TTLAttribute = "knativebrokerttl"
)

// GetTTL finds the TTL in the EventContext using a case insensitive comparison
// for the key. The second return param, is the case preserved key that matched.
// Depending on the encoding/transport, the extension case could be changed.
func GetTTL(ctx cloudevents.EventContext) (int32, error) {
	ttl, err := ctx.GetExtension(TTLAttribute)
	if err != nil {
		return 0, err
	}
	return cetypes.ToInteger(ttl)
}

// SetTTL sets the TTL into the EventContext. ttl should be a positive integer.
func SetTTL(ctx cloudevents.EventContext, ttl int32) error {
	return ctx.SetExtension(TTLAttribute, ttl)
}

// DeleteTTL removes the TTL CE extension attribute
func DeleteTTL(ctx cloudevents.EventContext) error {
	return ctx.SetExtension(TTLAttribute, nil)
}

// TTLDefaulter returns a cloudevents event defaulter that will manage the TTL
// for events with the following rules:
//   If TTL is not found, it will set it to the default passed in.
//   If TTL is <= 0, it will remain 0.
//   If TTL is > 1, it will be reduced by one.
func TTLDefaulter(logger *zap.Logger, defaultTTL int32) client.EventDefaulter {
	return func(ctx context.Context, event cloudevents.Event) cloudevents.Event {
		// Get the current or default TTL from the event.
		var ttl int32
		if ttlraw, err := event.Context.GetExtension(TTLAttribute); err != nil {
			logger.Debug("TTL not found in outbound event, defaulting.",
				zap.String("event.id", event.ID()),
				zap.Int32(TTLAttribute, defaultTTL),
				zap.Error(err),
			)
			ttl = defaultTTL
		} else if ttl, err = cetypes.ToInteger(ttlraw); err != nil {
			logger.Warn("Failed to convert existing TTL into integer, defaulting.",
				zap.String("event.id", event.ID()),
				zap.Any(TTLAttribute, ttlraw),
				zap.Error(err),
			)
			ttl = defaultTTL
		} else {
			// Decrement TTL.
			ttl = ttl - 1
			if ttl < 0 {
				ttl = 0
			}
		}
		// Overwrite the TTL into the event.
		if err := event.Context.SetExtension(TTLAttribute, ttl); err != nil {
			logger.Error("Failed to set TTL on outbound event.",
				zap.String("event.id", event.ID()),
				zap.Int32(TTLAttribute, ttl),
				zap.Error(err),
			)
		}

		return event
	}
}
