package eventbus

import (
	"context"

	"github.com/pkg/errors"

	"code.justin.tv/eventbus/client/internal/retrycount"
	"code.justin.tv/eventbus/client/internal/wire"
)

// Dispatcher parses an event payload and routes it to the correct handlers.
type Dispatcher interface {
	// Dispatch an incoming message to be routed to a handler.
	// This primarily is used by transport implementations but can also be used for testing.
	Dispatch(ctx context.Context, msg []byte) error
}

// A Handler processes one message coming from the event bus.
//
// Handlers are expected to do whatever work a subscriber does and return success/failure.
// Returning nil error means we have successfully done our work and this message can be
// marked delivered (depending on transport). Returning an error is communicated to
// the underlying transport and the result is usually re-delivery (though maybe not to
// the same machine/process).
//
// Some transports may use the Context to communicate deadlines and cancellation.
type Handler func(ctx context.Context, message RawMessage) error

type dispatcher struct {
	handlers map[string]Handler

	noopHandler    Handler
	defaultHandler Handler
}

func (d *dispatcher) Dispatch(ctx context.Context, msg []byte) error {
	h, payload, err := wire.HeaderAndPayload(msg)
	if err != nil {
		return &decodeError{err}
	}

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

	message := RawMessage{
		Header:  (*Header)(header),
		Payload: payload,
	}

	if wire.IsNoop(h) {
		return d.noopHandler(ctx, message)
	}

	handler, ok := d.handlers[header.EventType]
	if !ok {
		return d.defaultHandler(ctx, message)
	}

	return handler(ctx, message)
}

// Mux is used to register event handlers for subscribers.
type Mux struct {
	handlers map[string]Handler

	noopHandler    Handler
	defaultHandler Handler
}

func NewMux() *Mux {
	return &Mux{
		handlers: make(map[string]Handler),
	}
}

// Handle a specific event type with a handler function.
// If a handler already exists for a message type, Handle panics.
func (m *Mux) Handle(eventType string, f Handler) {
	if m.handlers[eventType] != nil {
		panic("Cannot register two handlers for " + eventType)
	}
	m.handlers[eventType] = f
}

// Advanced: Set a Handler that is called when a no-op message is received.
// Panics if a custom no-op handler has already been set.
//
// There is a default handler used by the mux if none is registered, so this
// exists primarily for debugging and special scenarios.
//
// A no-op handler that returns an error will cause the no-op message to be retried,
// just like any other handler errors.
func (m *Mux) HandleNoop(f Handler) {
	if m.noopHandler != nil {
		panic("only one no-op handler is allowed")
	}
	m.noopHandler = f
}

// Advanced: Set a Handler that is called for any event received with no specific handler.
// This can be used to customize how unrecognized messages are handled, such as
// logging them.
//
// If no handler is registered, the mux's default behavior is to return an error
// (which will cause the subscriber to retry the message).
func (m *Mux) HandleDefault(f Handler) {
	if m.defaultHandler != nil {
		panic("only one default handler is allowed")
	}
	m.defaultHandler = f
}

// Dispatcher creates a dispatcher from this mux.
func (m *Mux) Dispatcher() Dispatcher {
	clonedHandlers := make(map[string]Handler, len(m.handlers))
	for k, v := range m.handlers {
		clonedHandlers[k] = v
	}

	return &dispatcher{
		handlers:       clonedHandlers,
		noopHandler:    handlerFallback(m.noopHandler, defaultNoopHandler),
		defaultHandler: handlerFallback(m.defaultHandler, defaultDefaultHandler),
	}
}

// Deprecated: RegisterHandler is now superceded by 'Handle'
func (m *Mux) RegisterHandler(eventType string, f Handler) {
	m.Handle(eventType, f)
}

func RetryCountFromContext(ctx context.Context) (int, error) {
	rc, ok := retrycount.Count(ctx)
	if !ok {
		return 0, errors.New("could not retrieve retry count from provided context")
	}
	return rc, nil
}

func handlerFallback(candidate, fallback Handler) Handler {
	if candidate != nil {
		return candidate
	}
	return fallback
}

func defaultNoopHandler(ctx context.Context, message RawMessage) error {
	return nil
}

func defaultDefaultHandler(ctx context.Context, message RawMessage) error {
	if err := ctx.Err(); err != nil {
		return err
	}
	return &handlerNotFound{message.Header.EventType}
}
