package events

import (
	"context"
	"encoding/json"
	"strings"
	"time"

	"github.com/tech/sendico/pkg/merrors"
	"github.com/tech/sendico/pkg/mlogger"
	"go.mongodb.org/mongo-driver/v2/bson"
	"go.uber.org/zap"
)

type parserService struct {
	logger mlogger.Logger
}

// New creates event parser/payload builder service.
func New(logger mlogger.Logger) Service {
	if logger == nil {
		logger = zap.NewNop()
	}
	return &parserService{logger: logger.Named("events")}
}

func (s *parserService) Parse(data []byte) (*Envelope, error) {
	if len(data) == 0 {
		return nil, merrors.InvalidArgument("event payload is empty", "data")
	}

	var envelope Envelope
	if err := json.Unmarshal(data, &envelope); err != nil {
		return nil, merrors.InvalidArgumentWrap(err, "event payload is not valid JSON", "data")
	}

	if strings.TrimSpace(envelope.EventID) == "" {
		return nil, merrors.InvalidArgument("event_id is required", "event_id")
	}
	if strings.TrimSpace(envelope.Type) == "" {
		return nil, merrors.InvalidArgument("type is required", "type")
	}
	if envelope.OrganizationRef == bson.NilObjectID {
		return nil, merrors.InvalidArgument("organization_ref is required", "organization_ref")
	}
	if envelope.OccurredAt.IsZero() {
		return nil, merrors.InvalidArgument("occurred_at is required", "occurred_at")
	}
	if len(envelope.Data) == 0 {
		envelope.Data = []byte("{}")
	}

	envelope.EventID = strings.TrimSpace(envelope.EventID)
	envelope.Type = strings.TrimSpace(envelope.Type)
	envelope.OccurredAt = envelope.OccurredAt.UTC()
	if !envelope.PublishedAt.IsZero() {
		envelope.PublishedAt = envelope.PublishedAt.UTC()
	}

	return &envelope, nil
}

func (s *parserService) BuildPayload(_ context.Context, envelope *Envelope) ([]byte, error) {
	if envelope == nil {
		return nil, merrors.InvalidArgument("event envelope is required", "envelope")
	}

	payload := Payload{
		EventID:         envelope.EventID,
		Type:            envelope.Type,
		OrganizationRef: envelope.OrganizationRef,
		OccurredAt:      envelope.OccurredAt.UTC().Format(time.RFC3339Nano),
		Data:            envelope.Data,
	}
	if !envelope.PublishedAt.IsZero() {
		payload.PublishedAt = envelope.PublishedAt.UTC().Format(time.RFC3339Nano)
	}

	data, err := json.Marshal(payload)
	if err != nil {
		s.logger.Warn("Failed to marshal callback payload", zap.Error(err), zap.String("event_id", envelope.EventID))
		return nil, merrors.InternalWrap(err, "failed to marshal callback payload")
	}

	return data, nil
}
