package testhelpers

import (
	"errors"
	"time"

	"github.com/gofrs/uuid"

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

type EventBusBinary []byte

func (buf EventBusBinary) Bytes() []byte {
	return buf
}

// SNSPayload returns this string encoded as what you would put in the `Message` field of SNS
func (buf EventBusBinary) SNSPayload() string {
	return snsmarshal.EncodeToString(buf)
}

// Return a fake SNS message, for use in tests and validation
func (buf EventBusBinary) FakeSNSMessage() *SNSMessage {
	u := uuid.Must(uuid.NewV4())
	return &SNSMessage{
		MessageID: u.String(),
		Type:      "Notification",
		TopicArn:  "arn:aws:sns:us-west-2:123456789012:eventbus-fake",
		Message:   buf.SNSPayload(),
		Timestamp: time.Now().UTC(),
	}
}

// BinaryPayload DEPRECATED turns an event bus message into a binary event bus payload as expected by the Dispatcher.
//
// This can be used, for example, to build custom test harnesses or to prototype
// different transports for the event bus.
func BinaryPayload(message eventbus.Message, when time.Time, msgid uuid.UUID) (EventBusBinary, error) {
	header, err := wire.MakeHeader(msgid, message.EventBusName(), "", when)
	if err != nil {
		return nil, err
	}

	return wire.EncodeRaw(header, message)
}

// DefaultBinaryPayload DEPRECATED works like BinaryPayload except without customizing header values.
//
// IMPORTANT the output of this is not idempotent, as it uses the current time and a random UUID
// to provide its output. However, it does resemble the payload you get when you call Publish()
func DefaultBinaryPayload(message eventbus.Message) (EventBusBinary, error) {
	return wire.DefaultsEncode(message, "")
}

// MakePayload will make a binary payload similar to that made by the publisher.
//
// The header provided will be used to generate the binary header, but for
// convenience some defaults are provided:
//   - If MessageID is omitted, a random UUID from `uuid.NewV4()` is used.
//   - If CreatedAt is the zero value, then `time.Now().UTC()` is used.
//   - EventType is always set to `message.EventBusName()`.
//
func MakePayload(header *eventbus.Header, message eventbus.Message) (EventBusBinary, error) {
	if header.MessageID == uuid.Nil {
		var err error
		header.MessageID, err = uuid.NewV4()
		if err != nil {
			return nil, err
		}
	} else if header.MessageID.Version() != uuid.V4 {
		return nil, errors.New("UUID specified must be a v4 UUID")
	}

	if header.CreatedAt.IsZero() {
		header.CreatedAt = time.Now().UTC()
	}

	header.EventType = message.EventBusName()

	h, err := wire.MakeHeader(header.MessageID, header.EventType, header.Environment, header.CreatedAt)
	if err != nil {
		return nil, err
	}

	return wire.EncodeRaw(h, message)
}

// DecodeBinaryPayload takes the eventbus wire format and separates the header/payload.
func DecodeBinaryPayload(input []byte) (*eventbus.RawMessage, error) {
	h, payload, err := wire.HeaderAndPayload(input)
	if err != nil {
		return nil, err
	}

	header, err := wire.ToPublicHeader(h)
	if err != nil {
		return nil, err
	}

	return &eventbus.RawMessage{
		Header:  (*eventbus.Header)(header),
		Payload: payload,
	}, nil
}

// DecodeSNSPayload takes the eventbus SNS marshaled format and separates the header/payload.
//
// This expects a url-b64-encoded representation as is used in the sns `Message` property.
func DecodeSNSPayload(input string) (*eventbus.RawMessage, error) {
	buf, err := snsmarshal.DecodeString(input)
	if err != nil {
		return nil, err
	}
	return DecodeBinaryPayload(buf)
}
