package web

import (
	"io"
	"io/fs"
	"net/http"
	"strings"
	"time"

	"github.com/gin-gonic/gin"
)

// frontendFS holds the embedded SPA. It is populated by the build-tagged
// embed.go (compiled with -tags embedfrontend) and is nil otherwise, so default
// builds and `go test ./...` need no frontend build output to be present.
var frontendFS fs.FS

// RegisterStatic serves the embedded SPA under prefix (e.g. "/oncall"), matching
// the previous nginx behavior:
//   - hashed assets get long, immutable caching;
//   - the app shell (index.html) is never cached;
//   - unknown sub-paths fall back to index.html (SPA history routing).
//
// It also redirects the bare prefix and root to the app. It is a no-op when the
// frontend was not embedded, so non-Docker builds keep serving only the API.
func RegisterStatic(r *gin.Engine, prefix string) {
	if frontendFS == nil {
		return
	}
	prefix = "/" + strings.Trim(prefix, "/") // normalize to "/oncall"

	r.GET("/", func(c *gin.Context) { c.Redirect(http.StatusFound, prefix+"/") })
	r.GET(prefix, func(c *gin.Context) { c.Redirect(http.StatusMovedPermanently, prefix+"/") })
	r.GET(prefix+"/*filepath", func(c *gin.Context) {
		serveSPA(c, strings.TrimPrefix(c.Request.URL.Path, prefix+"/"))
	})
}

func serveSPA(c *gin.Context, rel string) {
	rel = strings.TrimPrefix(rel, "/")
	if rel == "" {
		rel = "index.html"
	}

	f, err := frontendFS.Open(rel)
	if err != nil {
		rel = "index.html" // history-mode fallback for client-side routes
		f, err = frontendFS.Open(rel)
		if err != nil {
			c.Status(http.StatusNotFound)
			return
		}
	}
	defer f.Close()

	seeker, ok := f.(io.ReadSeeker)
	if !ok {
		c.Status(http.StatusInternalServerError)
		return
	}

	if rel == "index.html" {
		c.Header("Cache-Control", "no-cache, no-store, must-revalidate")
	} else {
		c.Header("Cache-Control", "public, max-age=2592000, immutable")
	}
	http.ServeContent(c.Writer, c.Request, rel, time.Time{}, seeker)
}
