package handlers

import (
	"fmt"
	"net/http"
	"os"
	"path"
	"path/filepath"
	"strings"
)

// staticCacheControl mirrors app.py's _STATIC_CACHE_CONTROL: static assets
// aren't content-hashed in their filenames, so this has to stay short — it's
// a revalidation window, not a long-lived immutable cache. Still saves a full
// body re-transfer (the ETag/Last-Modified pair otherwise forces a
// conditional GET on every navigation) for anything fetched again within it.
// Stamped on every static file response, including 304s.
const staticCacheControl = "public, max-age=300"

// serveStatic is the catch-all under BASE — the Go analogue of app.py's
// _CachedStaticFiles mount, matching Starlette's StaticFiles semantics rather
// than http.FileServer's (which would 301 */index.html to the directory and
// render directory listings; Starlette 404s both —
// tests/unit/test_pages.py::test_old_static_index_is_gone pins the index.html
// case, keeping it from lingering as indexable duplicate content behind the
// server-rendered homepage).
func (s *Server) serveStatic(w http.ResponseWriter, r *http.Request) {
	rel := strings.TrimPrefix(r.URL.Path, s.base)
	// Trailing-slash paths never name a file; Starlette 404'd them (the
	// mount swallowed everything under BASE, so redirect_slashes never ran
	// for them).
	if strings.HasSuffix(rel, "/") {
		writePlain(w, http.StatusNotFound, "Not Found")
		return
	}
	// Rooted Clean cannot escape the static dir ("/../x" cleans to "/x");
	// the mux has already cleaned the request path, this is belt and braces
	// for handlers invoked directly (the content package's lazy IndexNow
	// route delegates here without re-cleaning).
	rel = path.Clean("/" + strings.TrimPrefix(rel, "/"))
	full := filepath.Join(s.staticDir, filepath.FromSlash(strings.TrimPrefix(rel, "/")))

	info, err := os.Stat(full)
	if err != nil || info.IsDir() {
		writePlain(w, http.StatusNotFound, "Not Found")
		return
	}
	f, err := os.Open(full)
	if err != nil {
		writePlain(w, http.StatusNotFound, "Not Found")
		return
	}
	defer f.Close()

	w.Header().Set("Cache-Control", staticCacheControl)
	// Starlette's StaticFiles stamped a stat-derived ETag (mtime+size hash);
	// same recipe here so If-None-Match revalidation keeps costing an empty
	// 304 instead of a body re-transfer. Headers set before ServeContent
	// survive on its 304/206 paths, Cache-Control included.
	w.Header().Set("ETag", fmt.Sprintf("\"%x-%x\"", info.ModTime().UnixNano(), info.Size()))
	http.ServeContent(w, r, info.Name(), info.ModTime(), f)
}
