package publisher

import (
	"context"
	"time"

	"github.com/aws/aws-sdk-go/aws"
	"github.com/aws/aws-sdk-go/service/sns"
	"github.com/aws/aws-sdk-go/service/sns/snsiface"
	"github.com/pkg/errors"

	"code.justin.tv/eventbus/client/internal/wire"
	"code.justin.tv/eventbus/client/lowlevel/snsmarshal"
)

const (
	noopBaseTimeout     = 15 * time.Second
	noopPerEventTimeout = 5 * time.Second
	noopSingleTimeout   = 20 * time.Second
)

func publishNoopsToRoutes(client snsiface.SNSAPI, routes map[string]*Route, environment Environment) error {
	// The first call to SNS will have no initialized TCP connections so it may be slower
	// than subsequent calls. So we have a base timeout plus add a bit more per event just in case.
	noopsTimeout := noopBaseTimeout + (noopPerEventTimeout * time.Duration(len(routes)))
	ctx, cancel := context.WithTimeout(context.Background(), noopsTimeout)
	defer cancel()

	for eventType, route := range routes {
		if !route.SuppressNoop {
			if err := publishNoop(ctx, client, route.Arn, environment); err != nil {
				return errors.Wrapf(err, "could not send no-op for %s", eventType)
			}
		}
	}
	return nil
}

// publishNoop sends a no-op message on publisher startup
func publishNoop(ctx context.Context, client snsiface.SNSAPI, topicARN string, environment Environment) error {
	buf, err := wire.DefaultEncodeNoop(string(environment))
	if err != nil {
		return errors.Wrap(err, "could not encode no-op")
	}
	encoded := snsmarshal.EncodeToString(buf)

	// The behavior of context.WithTimeout is to keep the shortest timeout of the parent ctx or this one.
	ctx, cancel := context.WithTimeout(ctx, noopSingleTimeout)
	defer cancel()

	_, err = client.PublishWithContext(ctx, &sns.PublishInput{
		Message: aws.String(encoded),
		MessageAttributes: map[string]*sns.MessageAttributeValue{
			snsmarshal.AttrIsNoop: {
				DataType:    snsmarshal.DataTypeNumber,
				StringValue: aws.String("1"),
			},
		},
		TopicArn: aws.String(topicARN),
	})
	return errors.Wrap(err, "could not publish noop to SNS")
}
