package connector_inbound

import (
	"encoding/json"
	"errors"
	"fmt"
)

// QueueEnvelope is the cross-process wrapper that the dispatcher writes into
// _System_ConnectorEvent.Payload and the worker reads back. It carries the
// typed metadata (ObjectKind, SourceID) that connector_events.Service.Enqueue
// does not store as dedicated columns. Worker → BatchRead receives env.Payload
// (the original user payload, unmodified).
//
// This struct is the single SSoT for the dispatcher→worker cross-process contract.
// No reserved-key injection into Payload bytes is required or permitted; all
// metadata is carried as typed top-level fields in this envelope.
type QueueEnvelope struct {
	// ObjectKind is the connector object kind (e.g. "contact"). Mirrors
	// connectorcapabilities.ConnectorEvent.ObjectKind.
	ObjectKind string `json:"objectKind"`
	// SourceID is the connector-side object identifier (e.g. provider's recordID).
	// Mirrors connectorcapabilities.ConnectorEvent.SourceID.
	SourceID string `json:"sourceID"`
	// Payload is the original user payload bytes as produced by
	// EnvelopeParserHandler.Parse — no reserved-key injection.
	Payload json.RawMessage `json:"payload"`
}

// ErrInvalidQueueEnvelope is returned when DecodeQueueEnvelope encounters bytes
// that are not a valid QueueEnvelope (malformed JSON, missing required fields).
var ErrInvalidQueueEnvelope = errors.New("connector_inbound: invalid queue envelope")

// EncodeQueueEnvelope serializes typed metadata + payload into the bytes that
// the dispatcher passes to connector_events.Service.Enqueue. The caller is
// responsible for ensuring objectKind and sourceID are non-empty (the dispatcher
// validates this before calling EncodeQueueEnvelope).
func EncodeQueueEnvelope(objectKind, sourceID string, payload json.RawMessage) ([]byte, error) {
	if objectKind == "" {
		return nil, fmt.Errorf("%w: objectKind is empty", ErrInvalidQueueEnvelope)
	}
	if sourceID == "" {
		return nil, fmt.Errorf("%w: sourceID is empty", ErrInvalidQueueEnvelope)
	}
	if len(payload) == 0 {
		payload = json.RawMessage("null")
	}
	return json.Marshal(QueueEnvelope{
		ObjectKind: objectKind,
		SourceID:   sourceID,
		Payload:    payload,
	})
}

// DecodeQueueEnvelope deserializes the queue payload bytes back into a typed
// envelope. Returns ErrInvalidQueueEnvelope (wrapped) if the bytes are not a
// valid envelope — the worker treats this as a parser-bug-class terminal failure.
func DecodeQueueEnvelope(b []byte) (QueueEnvelope, error) {
	if len(b) == 0 {
		return QueueEnvelope{}, fmt.Errorf("%w: empty bytes", ErrInvalidQueueEnvelope)
	}
	var env QueueEnvelope
	if err := json.Unmarshal(b, &env); err != nil {
		return QueueEnvelope{}, fmt.Errorf("%w: %v", ErrInvalidQueueEnvelope, err)
	}
	if env.ObjectKind == "" {
		return QueueEnvelope{}, fmt.Errorf("%w: missing objectKind", ErrInvalidQueueEnvelope)
	}
	if env.SourceID == "" {
		return QueueEnvelope{}, fmt.Errorf("%w: missing sourceID", ErrInvalidQueueEnvelope)
	}
	return env, nil
}
