package queue

import (
	"context"
	"errors"
	"strings"
	"time"

	"wucher/internal/shared/pkg/uuidv7"
)

const (
	FileProcessingKind = "file_manager.processing"
)

var (
	ErrInvalidFileProcessingJob      = errors.New("invalid file processing job")
	ErrInvalidFileProcessingEnvelope = errors.New("invalid file processing envelope")
)

type FileProcessingJob struct {
	JobID      string    `json:"job_id,omitempty"`
	FileUUID   string    `json:"file_uuid"`
	Bucket     string    `json:"bucket"`
	ObjectKey  string    `json:"object_key"`
	UploadedBy string    `json:"uploaded_by,omitempty"`
	MimeType   string    `json:"mime_type,omitempty"`
	SizeBytes  int64     `json:"size_bytes"`
	CreatedAt  time.Time `json:"created_at,omitempty"`
	TraceID    string    `json:"trace_id,omitempty"`
	RequestID  string    `json:"request_id,omitempty"`
}

func (j FileProcessingJob) Canonical() FileProcessingJob {
	job := j
	job.JobID = strings.TrimSpace(job.JobID)
	job.FileUUID = strings.TrimSpace(job.FileUUID)
	job.Bucket = strings.TrimSpace(job.Bucket)
	job.ObjectKey = strings.TrimSpace(job.ObjectKey)
	job.UploadedBy = strings.TrimSpace(job.UploadedBy)
	job.MimeType = strings.ToLower(strings.TrimSpace(job.MimeType))
	job.TraceID = strings.TrimSpace(job.TraceID)
	job.RequestID = strings.TrimSpace(job.RequestID)
	return job
}

func (j FileProcessingJob) Validate() error {
	job := j.Canonical()
	if job.FileUUID == "" {
		return errors.Join(ErrInvalidFileProcessingJob, errors.New("file uuid is required"))
	}
	if _, err := uuidv7.ParseString(job.FileUUID); err != nil {
		return errors.Join(ErrInvalidFileProcessingJob, errors.New("file uuid is invalid"))
	}
	if job.Bucket == "" {
		return errors.Join(ErrInvalidFileProcessingJob, errors.New("bucket is required"))
	}
	if job.ObjectKey == "" {
		return errors.Join(ErrInvalidFileProcessingJob, errors.New("object key is required"))
	}
	if job.SizeBytes < 0 {
		return errors.Join(ErrInvalidFileProcessingJob, errors.New("size bytes must be non-negative"))
	}
	return nil
}

type FileProcessingEnvelope struct {
	Version        string            `json:"version"`
	MessageID      string            `json:"message_id"`
	Kind           string            `json:"kind"`
	OccurredAt     time.Time         `json:"occurred_at"`
	CorrelationID  string            `json:"correlation_id,omitempty"`
	TraceID        string            `json:"trace_id,omitempty"`
	IdempotencyKey string            `json:"idempotency_key,omitempty"`
	Payload        FileProcessingJob `json:"payload"`
}

func NewFileProcessingEnvelope(ctx context.Context, version string, job FileProcessingJob) (*FileProcessingEnvelope, error) {
	job = job.Canonical()
	if err := job.Validate(); err != nil {
		return nil, err
	}
	messageID, err := newMessageID()
	if err != nil {
		return nil, err
	}
	correlationID := CorrelationIDFromContext(ctx)
	if correlationID == "" {
		correlationID = messageID
	}
	traceID := TraceIDFromContext(ctx)
	if traceID == "" {
		traceID = correlationID
	}
	now := time.Now().UTC()
	if strings.TrimSpace(version) == "" {
		version = "v1"
	}
	if job.JobID == "" {
		job.JobID = messageID
	}
	if job.CreatedAt.IsZero() {
		job.CreatedAt = now
	}
	if job.TraceID == "" {
		job.TraceID = traceID
	}
	if job.RequestID == "" {
		job.RequestID = correlationID
	}
	return &FileProcessingEnvelope{
		Version:        version,
		MessageID:      messageID,
		Kind:           FileProcessingKind,
		OccurredAt:     now,
		CorrelationID:  correlationID,
		TraceID:        traceID,
		IdempotencyKey: job.JobID,
		Payload:        job,
	}, nil
}

func (e FileProcessingEnvelope) DeduplicationKey() string {
	if v := strings.TrimSpace(e.IdempotencyKey); v != "" {
		return v
	}
	return strings.TrimSpace(e.MessageID)
}

type FileProcessingMessageSerializer interface {
	Serialize(ctx context.Context, job FileProcessingJob) (*OutboundMessage, error)
	Deserialize(body []byte, attributes map[string]string) (*FileProcessingEnvelope, error)
}

type FileProcessingJobProducer interface {
	Enqueue(ctx context.Context, job FileProcessingJob) error
}

type FileProcessingJobHandler interface {
	Handle(ctx context.Context, envelope *FileProcessingEnvelope) error
}
