package queue

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

type FileProcessingJSONSerializer struct {
	version        string
	queueType      string
	messageGroupID string
}

func NewFileProcessingJSONSerializer(version, queueType, messageGroupID string) *FileProcessingJSONSerializer {
	version = strings.TrimSpace(version)
	if version == "" {
		version = "v1"
	}
	queueType = strings.ToLower(strings.TrimSpace(queueType))
	if queueType == "" {
		queueType = QueueTypeStandard
	}
	return &FileProcessingJSONSerializer{
		version:        version,
		queueType:      queueType,
		messageGroupID: strings.TrimSpace(messageGroupID),
	}
}

func (s *FileProcessingJSONSerializer) Serialize(ctx context.Context, job FileProcessingJob) (*OutboundMessage, error) {
	envelope, err := NewFileProcessingEnvelope(ctx, s.version, job)
	if err != nil {
		return nil, err
	}
	body, err := json.Marshal(envelope)
	if err != nil {
		return nil, err
	}
	message := &OutboundMessage{
		ID:   envelope.MessageID,
		Body: body,
		Attributes: map[string]string{
			"message_kind":   envelope.Kind,
			"message_id":     envelope.MessageID,
			"correlation_id": envelope.CorrelationID,
			"trace_id":       envelope.TraceID,
			"schema_version": envelope.Version,
			"job_type":       "file_processing",
			"file_uuid":      envelope.Payload.FileUUID,
		},
	}
	if s.queueType == QueueTypeFIFO {
		message.MessageGroupID = s.messageGroupID
		if message.MessageGroupID == "" {
			message.MessageGroupID = "file-processing"
		}
		message.DeduplicationID = envelope.DeduplicationKey()
	}
	return message, nil
}

func (s *FileProcessingJSONSerializer) Deserialize(body []byte, _ map[string]string) (*FileProcessingEnvelope, error) {
	var envelope FileProcessingEnvelope
	if err := json.Unmarshal(body, &envelope); err != nil {
		return nil, errors.Join(ErrInvalidFileProcessingEnvelope, err)
	}
	if err := validateFileProcessingEnvelope(&envelope); err != nil {
		return nil, err
	}
	return &envelope, nil
}

func validateFileProcessingEnvelope(envelope *FileProcessingEnvelope) error {
	if envelope == nil {
		return ErrInvalidFileProcessingEnvelope
	}
	if strings.TrimSpace(envelope.Version) == "" {
		return errors.Join(ErrInvalidFileProcessingEnvelope, errors.New("version is required"))
	}
	if strings.TrimSpace(envelope.MessageID) == "" {
		return errors.Join(ErrInvalidFileProcessingEnvelope, errors.New("message id is required"))
	}
	if strings.TrimSpace(envelope.Kind) == "" {
		return errors.Join(ErrInvalidFileProcessingEnvelope, errors.New("kind is required"))
	}
	if !strings.EqualFold(strings.TrimSpace(envelope.Kind), FileProcessingKind) {
		return errors.Join(ErrInvalidFileProcessingEnvelope, errors.New("unsupported message kind"))
	}
	if envelope.OccurredAt.IsZero() {
		return errors.Join(ErrInvalidFileProcessingEnvelope, errors.New("occurred at is required"))
	}
	envelope.Payload = envelope.Payload.Canonical()
	if err := envelope.Payload.Validate(); err != nil {
		return errors.Join(ErrInvalidFileProcessingEnvelope, err)
	}
	if strings.TrimSpace(envelope.CorrelationID) == "" {
		envelope.CorrelationID = envelope.MessageID
	}
	if strings.TrimSpace(envelope.TraceID) == "" {
		envelope.TraceID = envelope.CorrelationID
	}
	if strings.TrimSpace(envelope.IdempotencyKey) == "" {
		envelope.IdempotencyKey = envelope.MessageID
	}
	if envelope.Payload.CreatedAt.IsZero() {
		envelope.Payload.CreatedAt = time.Now().UTC()
	}
	return nil
}
