package service

import (
	"context"
	"errors"
	"log/slog"
	"net"
	"strings"

	"github.com/aws/smithy-go"

	"wucher/internal/queue"
)

type EmailDeliveryHandler struct {
	sender EmailSender
	logger *slog.Logger
}

func NewEmailDeliveryHandler(sender EmailSender, logger *slog.Logger) *EmailDeliveryHandler {
	if logger == nil {
		logger = slog.Default()
	}
	return &EmailDeliveryHandler{
		sender: sender,
		logger: logger,
	}
}

func (h *EmailDeliveryHandler) Handle(ctx context.Context, envelope *queue.Envelope) error {
	if h.sender == nil {
		return queue.Permanent(errors.New("email sender is required"))
	}
	if envelope == nil {
		return queue.Permanent(errors.New("email envelope is required"))
	}
	if err := envelope.Payload.Validate(); err != nil {
		return queue.Permanent(err)
	}

	job := envelope.Payload
	shouldSkipSend := strings.EqualFold(strings.TrimSpace(job.Metadata["skip_send"]), "true")
	// Defense-in-depth: register verification emails should never be delivered.
	if !shouldSkipSend &&
		strings.EqualFold(strings.TrimSpace(job.MessageType), "email_verify") &&
		strings.EqualFold(strings.TrimSpace(job.Tags["source"]), "register") {
		shouldSkipSend = true
	}
	if shouldSkipSend {
		h.logger.Info("email delivery skipped",
			slog.String("message_id", envelope.MessageID),
			slog.String("job_type", job.NormalizedType()),
			slog.String("recipient_hash", job.RecipientHash()),
			slog.String("correlation_id", envelope.CorrelationID),
		)
		return nil
	}
	result, err := h.sender.Send(ctx, job)
	if err != nil {
		classified := classifyDeliveryError(err)
		h.logger.Error("email delivery failed",
			slog.String("message_id", envelope.MessageID),
			slog.String("job_type", job.NormalizedType()),
			slog.String("recipient_hash", job.RecipientHash()),
			slog.String("correlation_id", envelope.CorrelationID),
			slog.Any("error", err),
		)
		return classified
	}

	h.logger.Info("email delivered",
		slog.String("message_id", envelope.MessageID),
		slog.String("job_type", job.NormalizedType()),
		slog.String("recipient_hash", job.RecipientHash()),
		slog.String("correlation_id", envelope.CorrelationID),
		slog.String("provider", result.Provider),
		slog.String("provider_message_id", result.ProviderMessageID),
		slog.String("provider_request_id", result.RequestID),
	)
	return nil
}

func classifyDeliveryError(err error) error {
	if err == nil {
		return nil
	}
	if errors.Is(err, context.Canceled) || errors.Is(err, context.DeadlineExceeded) {
		return queue.Transient(err)
	}
	var netErr net.Error
	if errors.As(err, &netErr) {
		return queue.Transient(err)
	}
	var apiErr smithy.APIError
	if errors.As(err, &apiErr) {
		code := strings.ToLower(strings.TrimSpace(apiErr.ErrorCode()))
		switch code {
		case "throttling", "throttlingexception", "toomanyrequestsexception", "serviceunavailableexception", "internalserviceerror", "sendingpausedexception":
			return queue.Transient(err)
		case "message_rejected", "messagerejected", "badrequestexception", "mailfromdomainnotverifiedexception", "configurationsetdoesnotexistexception", "notfoundexception":
			return queue.Permanent(err)
		}
	}
	message := strings.ToLower(strings.TrimSpace(err.Error()))
	switch {
	case strings.Contains(message, "recipient is required"):
		return queue.Permanent(err)
	case strings.Contains(message, "subject is required"):
		return queue.Permanent(err)
	case strings.Contains(message, "body is required"):
		return queue.Permanent(err)
	case strings.Contains(message, "invalid email job"):
		return queue.Permanent(err)
	case strings.Contains(message, "ses is not configured"):
		return queue.Permanent(err)
	default:
		return queue.Transient(err)
	}
}
