package handler

import (
	"errors"
	"io"
	"mime"
	"net/http"
	"path/filepath"
	"strings"

	"github.com/gin-gonic/gin"

	"github.com/warmbly/warmbly/internal/infrastructure/storage"
)

// ServePublicObject streams a publicly-readable blob (avatar, org logo) from the
// active storage backend. It exists for the filesystem backend, which has no
// authority to serve objects itself; the S3 backend returns object-storage URLs
// from PutPublic and never routes through here. Only the fixed public key
// prefixes are served so this can't be used to read arbitrary stored objects.
func (h *Handler) ServePublicObject(c *gin.Context) {
	key := strings.TrimPrefix(c.Param("key"), "/")
	if key == "" || !isPublicKey(key) {
		c.Status(http.StatusNotFound)
		return
	}
	if h.Storage == nil {
		c.Status(http.StatusServiceUnavailable)
		return
	}

	body, err := h.Storage.Get(c.Request.Context(), key)
	if err != nil {
		if errors.Is(err, storage.ErrNotFound) {
			c.Status(http.StatusNotFound)
			return
		}
		c.Status(http.StatusInternalServerError)
		return
	}
	defer body.Close()

	if ct := mime.TypeByExtension(filepath.Ext(key)); ct != "" {
		c.Header("Content-Type", ct)
	}
	// Keys are content-addressed (they carry an epoch suffix), so they're safe
	// to cache immutably.
	c.Header("Cache-Control", "public, max-age=31536000, immutable")
	c.Status(http.StatusOK)
	_, _ = io.Copy(c.Writer, body)
}

// isPublicKey guards the /public route to the key prefixes PutPublic writes, so
// it can't be turned into a reader for arbitrary blob keys.
func isPublicKey(key string) bool {
	if strings.Contains(key, "..") {
		return false
	}
	return strings.HasPrefix(key, "avatars/") || strings.HasPrefix(key, "oauth-app-logos/") ||
		strings.HasPrefix(key, "form-assets/")
}
