package handler

import (
	"crypto/sha256"
	"encoding/hex"
	"fmt"
	"io"
	"mime"
	"net/http"
	"os"
	"path/filepath"
	"sync"
	"time"
)

// etagCache caches (etag, modtime) keyed by absolute file path.
// Key: path, Value: etagCacheEntry
var etagCacheM sync.Map

type etagCacheEntry struct {
	modTime time.Time
	etag    string
}

// ServeFile serves a single file with ETag, conditional GET (If-None-Match,
// If-Modified-Since), and Cache-Control headers.
// fsPath is the absolute filesystem path to the file.
func ServeFile(w http.ResponseWriter, r *http.Request, fsPath string) {
	f, err := os.Open(fsPath)
	if err != nil {
		if os.IsNotExist(err) {
			http.Error(w, "Not Found", http.StatusNotFound)
		} else {
			http.Error(w, "Internal Server Error", http.StatusInternalServerError)
		}
		return
	}
	defer f.Close()

	info, err := f.Stat()
	if err != nil {
		http.Error(w, "Internal Server Error", http.StatusInternalServerError)
		return
	}
	if info.IsDir() {
		http.Error(w, "Not a file", http.StatusBadRequest)
		return
	}

	etag := computeETag(fsPath, info.ModTime(), f)

	// Set headers
	w.Header().Set("ETag", fmt.Sprintf(`"%s"`, etag))
	w.Header().Set("Cache-Control", "public, max-age=3600, must-revalidate")

	// Conditional GET: If-None-Match
	if match := r.Header.Get("If-None-Match"); match != "" {
		if match == fmt.Sprintf(`"%s"`, etag) {
			w.WriteHeader(http.StatusNotModified)
			return
		}
	}

	// Content-Type
	ext := filepath.Ext(fsPath)
	ct := mime.TypeByExtension(ext)
	if ct == "" {
		ct = "application/octet-stream"
	}
	w.Header().Set("Content-Type", ct)

	// Seek back to start after ETag computation
	if _, err := f.Seek(0, io.SeekStart); err != nil {
		http.Error(w, "Internal Server Error", http.StatusInternalServerError)
		return
	}

	http.ServeContent(w, r, info.Name(), info.ModTime(), f)
}

// computeETag returns the SHA-256 ETag for a file, using a modtime-keyed cache.
func computeETag(path string, modTime time.Time, f *os.File) string {
	if entry, ok := etagCacheM.Load(path); ok {
		cached := entry.(etagCacheEntry)
		if cached.modTime.Equal(modTime) {
			return cached.etag
		}
	}

	h := sha256.New()
	if _, err := f.Seek(0, io.SeekStart); err == nil {
		io.Copy(h, f)
	}
	etag := hex.EncodeToString(h.Sum(nil))[:32] // first 32 hex chars (128 bits) is sufficient

	etagCacheM.Store(path, etagCacheEntry{modTime: modTime, etag: etag})
	return etag
}
