package binding

import (
	"bytes"
	"context"

	"github.com/cloudevents/sdk-go/v2/binding/format"
	"github.com/cloudevents/sdk-go/v2/binding/spec"
	"github.com/cloudevents/sdk-go/v2/event"
)

type eventFormatKey int

const (
	formatEventStructured eventFormatKey = iota
)

// EventMessage type-converts a event.Event object to implement Message.
// This allows local event.Event objects to be sent directly via Sender.Send()
//     s.Send(ctx, binding.EventMessage(e))
// When an event is wrapped into a EventMessage, the original event could be
// potentially mutated. If you need to use the Event again, after wrapping it into
// an Event message, you should copy it before
type EventMessage event.Event

func ToMessage(e *event.Event) Message {
	return (*EventMessage)(e)
}

func (m *EventMessage) ReadEncoding() Encoding {
	return EncodingEvent
}

func (m *EventMessage) ReadStructured(ctx context.Context, builder StructuredWriter) error {
	f := GetOrDefaultFromCtx(ctx, formatEventStructured, format.JSON).(format.Format)
	b, err := f.Marshal((*event.Event)(m))
	if err != nil {
		return err
	}
	return builder.SetStructuredEvent(ctx, f, bytes.NewReader(b))
}

func (m *EventMessage) ReadBinary(ctx context.Context, b BinaryWriter) (err error) {
	err = eventContextToBinaryWriter(m.Context, b)
	if err != nil {
		return err
	}
	// Pass the body
	body := (*event.Event)(m).Data()
	if len(body) > 0 {
		err = b.SetData(bytes.NewReader(body))
		if err != nil {
			return err
		}
	}
	return nil
}

func (m *EventMessage) GetAttribute(k spec.Kind) (spec.Attribute, interface{}) {
	sv := spec.VS.Version(m.Context.GetSpecVersion())
	a := sv.AttributeFromKind(k)
	if a != nil {
		return a, a.Get(m.Context)
	}
	return nil, nil
}

func (m *EventMessage) GetExtension(name string) interface{} {
	ext, _ := m.Context.GetExtension(name)
	return ext
}

func eventContextToBinaryWriter(c event.EventContext, b BinaryWriter) (err error) {
	// Pass all attributes
	sv := spec.VS.Version(c.GetSpecVersion())
	for _, a := range sv.Attributes() {
		value := a.Get(c)
		if value != nil {
			err = b.SetAttribute(a, value)
		}
		if err != nil {
			return err
		}
	}
	// Pass all extensions
	for k, v := range c.GetExtensions() {
		err = b.SetExtension(k, v)
		if err != nil {
			return err
		}
	}
	return nil
}

func (*EventMessage) Finish(error) error { return nil }

var _ Message = (*EventMessage)(nil)               // Test it conforms to the interface
var _ MessageMetadataReader = (*EventMessage)(nil) // Test it conforms to the interface

// UseFormatForEvent configures which format to use when marshalling the event to structured mode
func UseFormatForEvent(ctx context.Context, f format.Format) context.Context {
	return context.WithValue(ctx, formatEventStructured, f)
}
