package httpx

import (
	"context"
	"crypto/hmac"
	"crypto/sha256"
	"crypto/subtle"
	"encoding/base64"
	"encoding/hex"
	"encoding/json"
	"fmt"
	"log"
	"net/http"
	"strconv"
	"strings"
	"time"

	"github.com/go-chi/chi/v5"

	"royal-pop-backend/internal/config"
	"royal-pop-backend/internal/database"
	"royal-pop-backend/internal/inventory"
	"royal-pop-backend/internal/mailer"
	"royal-pop-backend/internal/orders"
)

const clientSessionCookieName = "royal_pop_client_session"

type clientLoginRequest struct {
	Username string `json:"username"`
	Password string `json:"password"`
}

type clientStockUpdateRequest struct {
	Style          string `json:"style"`
	ColorwayID     string `json:"colorwayId"`
	FinishID       string `json:"finishId"`
	QuantityOnHand int64  `json:"quantityOnHand"`
	Notes          string `json:"notes"`
}

type clientOrderUpdateRequest struct {
	FulfillmentStatus string `json:"fulfillmentStatus"`
	ShippingCarrier   string `json:"shippingCarrier"`
	TrackingNumber    string `json:"trackingNumber"`
	FulfillmentNotes  string `json:"fulfillmentNotes"`
	ClearShippedAt    bool   `json:"clearShippedAt"`
	MarkShippedAtNow  bool   `json:"markShippedAtNow"`
}

func handleClientReseed(db *database.DB) http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		if db == nil {
			WriteError(w, http.StatusServiceUnavailable, "reseed_unavailable", "Database reseed is not configured yet.")
			return
		}

		ctx, cancel := context.WithTimeout(r.Context(), 30*time.Second)
		defer cancel()

		summary, err := db.BackupAndResetAppData(ctx)
		if err != nil {
			WriteError(w, http.StatusInternalServerError, "reseed_failed", "The database backup or reset could not be completed.")
			return
		}

		WriteJSON(w, http.StatusOK, map[string]any{
			"data": map[string]any{
				"backupPath":      summary.BackupPath,
				"ordersCount":     summary.OrdersCount,
				"orderItemsCount": summary.OrderItemsCount,
				"inventoryCount":  summary.InventoryCount,
				"message":         "Backup created and the local database has been reset to an empty state.",
			},
		})
	}
}

func handleListPublicInventory(stockStore *inventory.Store) http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		if stockStore == nil {
			WriteJSON(w, http.StatusOK, map[string]any{
				"data": map[string]any{
					"inventory": []map[string]any{},
				},
			})
			return
		}

		levels, err := stockStore.List(r.Context())
		if err != nil {
			WriteError(w, http.StatusInternalServerError, "inventory_list_failed", "The storefront could not load inventory availability.")
			return
		}

		serialized := make([]map[string]any, 0, len(levels))
		for _, level := range levels {
			serialized = append(serialized, map[string]any{
				"style":          level.Style,
				"colorwayId":     level.ColorwayID,
				"finishId":       level.FinishID,
				"quantityOnHand": level.QuantityOnHand,
				"updatedAt":      level.UpdatedAt.Format(time.RFC3339),
			})
		}

		WriteJSON(w, http.StatusOK, map[string]any{
			"data": map[string]any{
				"inventory": serialized,
			},
		})
	}
}

func handleGetClientSession(cfg *config.Config) http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		if !cfg.ClientDashboardEnabled() {
			WriteJSON(w, http.StatusOK, map[string]any{
				"data": map[string]any{
					"authenticated": false,
					"configured":    false,
				},
			})
			return
		}

		username, expiresAt, ok := validateClientSessionCookie(cfg, r)
		WriteJSON(w, http.StatusOK, map[string]any{
			"data": map[string]any{
				"authenticated": ok,
				"configured":    true,
				"username":      username,
				"expiresAt":     expiresAt,
			},
		})
	}
}

func handleClientLogin(cfg *config.Config) http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		if !cfg.ClientDashboardEnabled() {
			WriteError(w, http.StatusServiceUnavailable, "client_dashboard_not_configured", "Client dashboard credentials are not configured yet.")
			return
		}

		var request clientLoginRequest
		if err := json.NewDecoder(r.Body).Decode(&request); err != nil {
			WriteError(w, http.StatusBadRequest, "invalid_json", "Provide a valid username and password.")
			return
		}

		if subtle.ConstantTimeCompare([]byte(strings.TrimSpace(request.Username)), []byte(cfg.ClientDashboardUser)) != 1 ||
			subtle.ConstantTimeCompare([]byte(request.Password), []byte(cfg.ClientDashboardPass)) != 1 {
			WriteError(w, http.StatusUnauthorized, "invalid_client_credentials", "The client dashboard username or password is incorrect.")
			return
		}

		expiresAt := time.Now().Add(cfg.ClientDashboardSessionTTL)
		setClientSessionCookie(w, cfg, cfg.ClientDashboardUser, expiresAt)

		WriteJSON(w, http.StatusOK, map[string]any{
			"data": map[string]any{
				"authenticated": true,
				"configured":    true,
				"username":      cfg.ClientDashboardUser,
				"expiresAt":     expiresAt.Format(time.RFC3339),
			},
		})
	}
}

func handleClientLogout(cfg *config.Config) http.HandlerFunc {
	return func(w http.ResponseWriter, _ *http.Request) {
		clearClientSessionCookie(w, cfg)
		WriteJSON(w, http.StatusOK, map[string]any{
			"data": map[string]any{
				"authenticated": false,
			},
		})
	}
}

func requireClientSession(cfg *config.Config) func(http.Handler) http.Handler {
	return func(next http.Handler) http.Handler {
		return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
			if !cfg.ClientDashboardEnabled() {
				WriteError(w, http.StatusServiceUnavailable, "client_dashboard_not_configured", "Client dashboard credentials are not configured yet.")
				return
			}

			if _, _, ok := validateClientSessionCookie(cfg, r); !ok {
				WriteError(w, http.StatusUnauthorized, "client_dashboard_auth_required", "Please sign in to the client dashboard first.")
				return
			}

			next.ServeHTTP(w, r)
		})
	}
}

func handleListClientOrders(orderStore *orders.Store) http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		if orderStore == nil {
			WriteError(w, http.StatusServiceUnavailable, "orders_unavailable", "Order storage is not configured yet.")
			return
		}

		limit := 25
		if raw := strings.TrimSpace(r.URL.Query().Get("limit")); raw != "" {
			parsed, err := strconv.Atoi(raw)
			if err == nil {
				limit = parsed
			}
		}

		search := strings.TrimSpace(r.URL.Query().Get("search"))
		status := strings.TrimSpace(r.URL.Query().Get("status"))

		ordersList, err := orderStore.ListForDashboard(r.Context(), orders.ListFilter{
			Limit:             limit,
			Search:            search,
			FulfillmentStatus: status,
		})
		if err != nil {
			WriteError(w, http.StatusInternalServerError, "orders_list_failed", "The client dashboard could not load recent orders.")
			return
		}

		serialized := make([]map[string]any, 0, len(ordersList))
		for _, order := range ordersList {
			serialized = append(serialized, serializeClientOrderSummary(order))
		}

		WriteJSON(w, http.StatusOK, map[string]any{
			"data": map[string]any{
				"orders": serialized,
			},
		})
	}
}

func handleGetClientOrder(orderStore *orders.Store) http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		if orderStore == nil {
			WriteError(w, http.StatusServiceUnavailable, "orders_unavailable", "Order storage is not configured yet.")
			return
		}

		orderID := strings.TrimSpace(chi.URLParam(r, "orderID"))
		if orderID == "" {
			WriteError(w, http.StatusBadRequest, "invalid_order_id", "Provide a valid order ID.")
			return
		}

		order, err := orderStore.GetByID(r.Context(), orderID)
		if err != nil {
			if err == orders.ErrOrderNotFound {
				WriteError(w, http.StatusNotFound, "order_not_found", "That order could not be found.")
				return
			}
			WriteError(w, http.StatusInternalServerError, "order_load_failed", "The client dashboard could not load this order.")
			return
		}

		WriteJSON(w, http.StatusOK, map[string]any{
			"data": map[string]any{
				"order": serializeClientOrderDetail(order),
			},
		})
	}
}

func handleUpdateClientOrder(orderStore *orders.Store, fulfillmentMailer *mailer.ResendMailer) http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		if orderStore == nil {
			WriteError(w, http.StatusServiceUnavailable, "orders_unavailable", "Order storage is not configured yet.")
			return
		}

		orderID := strings.TrimSpace(chi.URLParam(r, "orderID"))
		if orderID == "" {
			WriteError(w, http.StatusBadRequest, "invalid_order_id", "Provide a valid order ID.")
			return
		}

		existingOrder, err := orderStore.GetByID(r.Context(), orderID)
		if err != nil {
			if err == orders.ErrOrderNotFound {
				WriteError(w, http.StatusNotFound, "order_not_found", "That order could not be found.")
				return
			}
			WriteError(w, http.StatusInternalServerError, "order_load_failed", "The client dashboard could not load this order.")
			return
		}

		var request clientOrderUpdateRequest
		if err := json.NewDecoder(r.Body).Decode(&request); err != nil {
			WriteError(w, http.StatusBadRequest, "invalid_json", "Provide a valid order update payload.")
			return
		}

		updatedOrder, err := orderStore.UpdateFulfillment(r.Context(), orders.UpdateFulfillmentInput{
			OrderID:           orderID,
			FulfillmentStatus: request.FulfillmentStatus,
			ShippingCarrier:   request.ShippingCarrier,
			TrackingNumber:    request.TrackingNumber,
			FulfillmentNotes:  request.FulfillmentNotes,
			ClearShippedAt:    request.ClearShippedAt,
			MarkShippedAtNow:  request.MarkShippedAtNow,
		})
		if err != nil {
			switch err {
			case orders.ErrOrderNotFound:
				WriteError(w, http.StatusNotFound, "order_not_found", "That order could not be found.")
				return
			default:
				if strings.Contains(err.Error(), "invalid fulfillment status") {
					WriteError(w, http.StatusBadRequest, "invalid_fulfillment_status", "Choose a valid fulfillment status.")
					return
				}
				WriteError(w, http.StatusInternalServerError, "order_update_failed", "The order update could not be saved.")
				return
			}
		}

		shipmentEmail := map[string]any{
			"status":  "skipped",
			"message": "Fulfilment email was not sent.",
		}

		if fulfillmentMailer == nil || !fulfillmentMailer.Enabled() {
			shipmentEmail["reason"] = "not_configured"
			shipmentEmail["message"] = "Fulfilment email is not configured."
		} else if shouldSendShipmentEmail(existingOrder, updatedOrder) {
			if err := fulfillmentMailer.SendOrderShipped(r.Context(), updatedOrder); err != nil {
				log.Printf("client order shipment email failed for %s: %v", updatedOrder.ID, err)
				shipmentEmail["status"] = "failed"
				shipmentEmail["message"] = "The order was updated, but the shipment email could not be sent."
				shipmentEmail["reason"] = "send_failed"
			} else {
				shipmentEmail["status"] = "sent"
				shipmentEmail["message"] = "Shipment email sent to the customer."
			}
		} else if shouldSendCancellationEmail(existingOrder, updatedOrder) {
			if err := fulfillmentMailer.SendOrderCancelled(r.Context(), updatedOrder); err != nil {
				log.Printf("client order cancellation email failed for %s: %v", updatedOrder.ID, err)
				shipmentEmail["status"] = "failed"
				shipmentEmail["message"] = "The order was updated, but the cancellation email could not be sent."
				shipmentEmail["reason"] = "send_failed"
			} else {
				shipmentEmail["status"] = "sent"
				shipmentEmail["message"] = "Cancellation email sent to the customer."
			}
		} else if shouldSendDeliveredEmail(existingOrder, updatedOrder) {
			if err := fulfillmentMailer.SendOrderDelivered(r.Context(), updatedOrder); err != nil {
				log.Printf("client order delivered email failed for %s: %v", updatedOrder.ID, err)
				shipmentEmail["status"] = "failed"
				shipmentEmail["message"] = "The order was updated, but the delivered email could not be sent."
				shipmentEmail["reason"] = "send_failed"
			} else {
				shipmentEmail["status"] = "sent"
				shipmentEmail["message"] = "Delivered email sent to the customer."
			}
		} else {
			shipmentEmail["reason"] = shipmentEmailSkipReason(existingOrder, updatedOrder)
		}

		WriteJSON(w, http.StatusOK, map[string]any{
			"data": map[string]any{
				"order":         serializeClientOrderDetail(updatedOrder),
				"shipmentEmail": shipmentEmail,
			},
		})
	}
}

func shouldSendShipmentEmail(previousOrder, updatedOrder *orders.Order) bool {
	if previousOrder == nil || updatedOrder == nil {
		return false
	}

	if !strings.EqualFold(updatedOrder.FulfillmentStatus, orders.FulfillmentShipped) {
		return false
	}

	if strings.EqualFold(previousOrder.FulfillmentStatus, orders.FulfillmentShipped) {
		return false
	}

	if strings.TrimSpace(updatedOrder.Email) == "" {
		return false
	}

	if strings.TrimSpace(updatedOrder.ShippingCarrier) == "" || strings.TrimSpace(updatedOrder.TrackingNumber) == "" {
		return false
	}

	return true
}

func shouldSendCancellationEmail(previousOrder, updatedOrder *orders.Order) bool {
	if previousOrder == nil || updatedOrder == nil {
		return false
	}

	if !strings.EqualFold(updatedOrder.FulfillmentStatus, orders.FulfillmentCancelled) {
		return false
	}

	if strings.EqualFold(previousOrder.FulfillmentStatus, orders.FulfillmentCancelled) {
		return false
	}

	if strings.TrimSpace(updatedOrder.Email) == "" {
		return false
	}

	return true
}

func shouldSendDeliveredEmail(previousOrder, updatedOrder *orders.Order) bool {
	if previousOrder == nil || updatedOrder == nil {
		return false
	}

	if !strings.EqualFold(updatedOrder.FulfillmentStatus, orders.FulfillmentDelivered) {
		return false
	}

	if strings.EqualFold(previousOrder.FulfillmentStatus, orders.FulfillmentDelivered) {
		return false
	}

	if strings.TrimSpace(updatedOrder.Email) == "" {
		return false
	}

	return true
}

func shipmentEmailSkipReason(previousOrder, updatedOrder *orders.Order) string {
	if updatedOrder == nil {
		return "order_unavailable"
	}

	if strings.EqualFold(updatedOrder.FulfillmentStatus, orders.FulfillmentShipped) {
		if previousOrder != nil && strings.EqualFold(previousOrder.FulfillmentStatus, orders.FulfillmentShipped) {
			return "already_shipped"
		}

		if strings.TrimSpace(updatedOrder.Email) == "" {
			return "missing_email"
		}

		if strings.TrimSpace(updatedOrder.ShippingCarrier) == "" || strings.TrimSpace(updatedOrder.TrackingNumber) == "" {
			return "missing_tracking"
		}

		return "not_applicable"
	}

	if strings.EqualFold(updatedOrder.FulfillmentStatus, orders.FulfillmentCancelled) {
		if previousOrder != nil && strings.EqualFold(previousOrder.FulfillmentStatus, orders.FulfillmentCancelled) {
			return "already_cancelled"
		}

		if strings.TrimSpace(updatedOrder.Email) == "" {
			return "missing_email"
		}

		return "not_applicable"
	}

	if strings.EqualFold(updatedOrder.FulfillmentStatus, orders.FulfillmentDelivered) {
		if previousOrder != nil && strings.EqualFold(previousOrder.FulfillmentStatus, orders.FulfillmentDelivered) {
			return "already_delivered"
		}

		if strings.TrimSpace(updatedOrder.Email) == "" {
			return "missing_email"
		}

		return "not_applicable"
	}

	return "status_not_supported"
}

func handleListClientStock(stockStore *inventory.Store) http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		if stockStore == nil {
			WriteError(w, http.StatusServiceUnavailable, "stock_unavailable", "Stock storage is not configured yet.")
			return
		}

		levels, err := stockStore.List(r.Context())
		if err != nil {
			WriteError(w, http.StatusInternalServerError, "stock_list_failed", "The client dashboard could not load stock levels.")
			return
		}

		serialized := make([]map[string]any, 0, len(levels))
		for _, level := range levels {
			serialized = append(serialized, map[string]any{
				"style":          level.Style,
				"colorwayId":     level.ColorwayID,
				"finishId":       level.FinishID,
				"quantityOnHand": level.QuantityOnHand,
				"notes":          level.Notes,
				"createdAt":      level.CreatedAt.Format(time.RFC3339),
				"updatedAt":      level.UpdatedAt.Format(time.RFC3339),
			})
		}

		WriteJSON(w, http.StatusOK, map[string]any{
			"data": map[string]any{
				"stock": serialized,
			},
		})
	}
}

func handleUpsertClientStock(stockStore *inventory.Store) http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		if stockStore == nil {
			WriteError(w, http.StatusServiceUnavailable, "stock_unavailable", "Stock storage is not configured yet.")
			return
		}

		var request clientStockUpdateRequest
		if err := json.NewDecoder(r.Body).Decode(&request); err != nil {
			WriteError(w, http.StatusBadRequest, "invalid_json", "Provide a valid stock update payload.")
			return
		}

		request.Style = strings.TrimSpace(strings.ToUpper(request.Style))
		request.ColorwayID = strings.TrimSpace(request.ColorwayID)
		request.FinishID = strings.TrimSpace(request.FinishID)
		request.Notes = strings.TrimSpace(request.Notes)

		if request.Style != "A" && request.Style != "B" {
			WriteError(w, http.StatusBadRequest, "invalid_style", "Stock entries must use style A or B.")
			return
		}
		if request.ColorwayID == "" || request.FinishID == "" {
			WriteError(w, http.StatusBadRequest, "invalid_stock_key", "Stock entries need a colorway and finish.")
			return
		}
		if request.QuantityOnHand < 0 {
			WriteError(w, http.StatusBadRequest, "invalid_quantity", "Stock quantity cannot be negative.")
			return
		}

		level, err := stockStore.Upsert(r.Context(), inventory.UpsertInput{
			Style:          request.Style,
			ColorwayID:     request.ColorwayID,
			FinishID:       request.FinishID,
			QuantityOnHand: request.QuantityOnHand,
			Notes:          request.Notes,
		})
		if err != nil {
			WriteError(w, http.StatusInternalServerError, "stock_save_failed", "The stock level could not be saved.")
			return
		}

		WriteJSON(w, http.StatusOK, map[string]any{
			"data": map[string]any{
				"stock": map[string]any{
					"style":          level.Style,
					"colorwayId":     level.ColorwayID,
					"finishId":       level.FinishID,
					"quantityOnHand": level.QuantityOnHand,
					"notes":          level.Notes,
					"createdAt":      level.CreatedAt.Format(time.RFC3339),
					"updatedAt":      level.UpdatedAt.Format(time.RFC3339),
				},
			},
		})
	}
}

func validateClientSessionCookie(cfg *config.Config, r *http.Request) (string, string, bool) {
	if cfg == nil || r == nil || !cfg.ClientDashboardEnabled() {
		return "", "", false
	}

	cookie, err := r.Cookie(clientSessionCookieName)
	if err != nil || strings.TrimSpace(cookie.Value) == "" {
		return "", "", false
	}

	decoded, err := base64.RawURLEncoding.DecodeString(cookie.Value)
	if err != nil {
		return "", "", false
	}

	parts := strings.Split(string(decoded), ".")
	if len(parts) != 3 {
		return "", "", false
	}

	username := parts[0]
	expiresUnix, err := strconv.ParseInt(parts[1], 10, 64)
	if err != nil {
		return "", "", false
	}

	expiresAt := time.Unix(expiresUnix, 0).UTC()
	if time.Now().After(expiresAt) {
		return "", "", false
	}

	expectedSignature := signClientSession(cfg.ClientDashboardSigningSecret(), username, expiresUnix)
	if subtle.ConstantTimeCompare([]byte(parts[2]), []byte(expectedSignature)) != 1 {
		return "", "", false
	}

	if subtle.ConstantTimeCompare([]byte(username), []byte(cfg.ClientDashboardUser)) != 1 {
		return "", "", false
	}

	return username, expiresAt.Format(time.RFC3339), true
}

func setClientSessionCookie(w http.ResponseWriter, cfg *config.Config, username string, expiresAt time.Time) {
	expiresUnix := expiresAt.UTC().Unix()
	token := fmt.Sprintf("%s.%d.%s", username, expiresUnix, signClientSession(cfg.ClientDashboardSigningSecret(), username, expiresUnix))

	http.SetCookie(w, &http.Cookie{
		Name:     clientSessionCookieName,
		Value:    base64.RawURLEncoding.EncodeToString([]byte(token)),
		Path:     "/",
		Expires:  expiresAt,
		MaxAge:   int(time.Until(expiresAt).Seconds()),
		HttpOnly: true,
		SameSite: http.SameSiteLaxMode,
		Secure:   !cfg.IsDevelopment(),
	})
}

func clearClientSessionCookie(w http.ResponseWriter, cfg *config.Config) {
	http.SetCookie(w, &http.Cookie{
		Name:     clientSessionCookieName,
		Value:    "",
		Path:     "/",
		Expires:  time.Unix(0, 0),
		MaxAge:   -1,
		HttpOnly: true,
		SameSite: http.SameSiteLaxMode,
		Secure:   !cfg.IsDevelopment(),
	})
}

func signClientSession(secret, username string, expiresUnix int64) string {
	mac := hmac.New(sha256.New, []byte(secret))
	_, _ = mac.Write([]byte(fmt.Sprintf("%s|%d", username, expiresUnix)))
	return hex.EncodeToString(mac.Sum(nil))
}

func serializeClientOrderSummary(order orders.Order) map[string]any {
	itemCount := int64(0)
	items := make([]map[string]any, 0, len(order.Items))
	for _, item := range order.Items {
		itemCount += item.Quantity
		items = append(items, map[string]any{
			"style":      item.Style,
			"colorwayId": item.ColorwayID,
			"finishId":   item.FinishID,
			"quantity":   item.Quantity,
			"unitAmount": item.UnitAmount,
		})
	}

	return map[string]any{
		"id":                order.ID,
		"status":            order.Status,
		"fulfillmentStatus": order.FulfillmentStatus,
		"email":             order.Email,
		"phone":             order.Phone,
		"firstName":         order.FirstName,
		"lastName":          order.LastName,
		"city":              order.City,
		"region":            order.Region,
		"postalCode":        order.PostalCode,
		"country":           order.Country,
		"notes":             order.Notes,
		"currency":          order.Currency,
		"amount":            order.Amount,
		"shippingCarrier":   order.ShippingCarrier,
		"trackingNumber":    order.TrackingNumber,
		"webhookStatus":     order.WebhookStatus,
		"webhookMessage":    order.WebhookMessage,
		"createdAt":         order.CreatedAt.Format(time.RFC3339),
		"updatedAt":         order.UpdatedAt.Format(time.RFC3339),
		"shippedAt":         formatOptionalTime(order.ShippedAt),
		"itemCount":         itemCount,
		"items":             items,
	}
}

func serializeClientOrderDetail(order *orders.Order) map[string]any {
	if order == nil {
		return map[string]any{}
	}

	items := make([]map[string]any, 0, len(order.Items))
	for _, item := range order.Items {
		items = append(items, map[string]any{
			"itemId":     item.ItemID,
			"style":      item.Style,
			"colorwayId": item.ColorwayID,
			"finishId":   item.FinishID,
			"quantity":   item.Quantity,
			"unitAmount": item.UnitAmount,
		})
	}

	return map[string]any{
		"id":                    order.ID,
		"status":                order.Status,
		"fulfillmentStatus":     order.FulfillmentStatus,
		"email":                 order.Email,
		"phone":                 order.Phone,
		"firstName":             order.FirstName,
		"lastName":              order.LastName,
		"addressLine1":          order.AddressLine1,
		"addressLine2":          order.AddressLine2,
		"city":                  order.City,
		"region":                order.Region,
		"postalCode":            order.PostalCode,
		"country":               order.Country,
		"notes":                 order.Notes,
		"currency":              order.Currency,
		"amount":                order.Amount,
		"shippingCarrier":       order.ShippingCarrier,
		"trackingNumber":        order.TrackingNumber,
		"fulfillmentNotes":      order.FulfillmentNotes,
		"webhookStatus":         order.WebhookStatus,
		"webhookEventId":        order.WebhookEventID,
		"webhookEventType":      order.WebhookEventType,
		"webhookMessage":        order.WebhookMessage,
		"stripePaymentIntentId": order.StripePaymentIntentID,
		"createdAt":             order.CreatedAt.Format(time.RFC3339),
		"updatedAt":             order.UpdatedAt.Format(time.RFC3339),
		"shippedAt":             formatOptionalTime(order.ShippedAt),
		"items":                 items,
	}
}

func formatOptionalTime(value *time.Time) any {
	if value == nil {
		return nil
	}
	return value.UTC().Format(time.RFC3339)
}
