package main

// Tests for the WI-03 split of the new.html monolith into hashed static
// assets (static/rcc.css + static/rcc.js). Uses the REAL embedded FS.
//
// Contract under test:
//   - rcc.css / rcc.js exist in the embed and are non-trivial.
//   - new.html references them via ?v=__RCC_CSS_V__ / __RCC_JS_V__
//     placeholders and contains NO inline <style> or inline <script> body.
//   - The startup stamping (mirrored from httpapi.go registerRoutes)
//     replaces every placeholder with the sha8 of the asset body.
//   - serveHashedAsset semantics: 200 + immutable Cache-Control + ETag,
//     and 304 on If-None-Match.

import (
	"bytes"
	"crypto/sha256"
	"encoding/hex"
	"io"
	"io/fs"
	"net/http"
	"net/http/httptest"
	"regexp"
	"strings"
	"testing"
	"time"
)

func testSha8(b []byte) string {
	sum := sha256.Sum256(b)
	return hex.EncodeToString(sum[:4])
}

func TestRCCAssetsEmbeddedAndStamped(t *testing.T) {
	sub, err := fs.Sub(staticFS, "static")
	if err != nil {
		t.Fatalf("fs.Sub: %v", err)
	}
	css, err := fs.ReadFile(sub, "rcc.css")
	if err != nil {
		t.Fatalf("rcc.css missing from embed: %v", err)
	}
	js, err := fs.ReadFile(sub, "rcc.js")
	if err != nil {
		t.Fatalf("rcc.js missing from embed: %v", err)
	}
	html, err := fs.ReadFile(sub, "new.html")
	if err != nil {
		t.Fatalf("new.html missing from embed: %v", err)
	}
	if len(css) < 100_000 || len(js) < 500_000 {
		t.Fatalf("asset sizes suspicious: css=%d js=%d — extraction regressed?", len(css), len(js))
	}
	if bytes.Contains(html, []byte("<style>")) {
		t.Error("new.html still contains an inline <style> block")
	}
	// The only <script> tag must be the src= one (no inline JS body).
	scriptTags := regexp.MustCompile(`<script[^>]*>`).FindAllString(string(html), -1)
	if len(scriptTags) != 1 || !strings.Contains(scriptTags[0], `src="/rcc.js?v=`) {
		t.Errorf("new.html script tags = %q — want exactly one src=/rcc.js tag", scriptTags)
	}
	if !bytes.Contains(html, []byte(`/rcc.css?v=__RCC_CSS_V__`)) {
		t.Error("new.html missing rcc.css placeholder reference")
	}

	// Mirror the registerRoutes stamping.
	stamped := bytes.ReplaceAll(html, []byte("__RCC_CSS_V__"), []byte(testSha8(css)))
	stamped = bytes.ReplaceAll(stamped, []byte("__RCC_JS_V__"), []byte(testSha8(js)))
	if bytes.Contains(stamped, []byte("__RCC_")) {
		t.Error("placeholder survived stamping")
	}
	if !bytes.Contains(stamped, []byte("/rcc.css?v="+testSha8(css))) ||
		!bytes.Contains(stamped, []byte("/rcc.js?v="+testSha8(js))) {
		t.Error("stamped html missing hashed asset URLs")
	}
}

// Mirrors serveHashedAsset in httpapi.go (kept in sync by hand, like
// buildRootPanel in rootpanel_loop_test.go).
func buildHashedAssetHandler(body []byte, ctype string) http.HandlerFunc {
	etag := `"` + testSha8(body) + `"`
	return func(w http.ResponseWriter, r *http.Request) {
		w.Header().Set("Content-Type", ctype)
		w.Header().Set("Cache-Control", "public, max-age=31536000, immutable")
		w.Header().Set("ETag", etag)
		if strings.Contains(r.Header.Get("If-None-Match"), etag) {
			w.WriteHeader(http.StatusNotModified)
			return
		}
		http.ServeContent(w, r, "", time.Time{}, bytes.NewReader(body))
	}
}

func TestRCCAssetCachingSemantics(t *testing.T) {
	sub, _ := fs.Sub(staticFS, "static")
	css, err := fs.ReadFile(sub, "rcc.css")
	if err != nil {
		t.Fatalf("rcc.css: %v", err)
	}
	srv := httptest.NewServer(buildHashedAssetHandler(css, "text/css; charset=utf-8"))
	defer srv.Close()

	resp, err := http.Get(srv.URL + "/rcc.css?v=" + testSha8(css))
	if err != nil {
		t.Fatalf("GET: %v", err)
	}
	body, _ := io.ReadAll(resp.Body)
	resp.Body.Close()
	if resp.StatusCode != 200 || !bytes.Equal(body, css) {
		t.Fatalf("status=%d len=%d want 200 + %d bytes", resp.StatusCode, len(body), len(css))
	}
	if cc := resp.Header.Get("Cache-Control"); !strings.Contains(cc, "immutable") {
		t.Errorf("Cache-Control=%q — want immutable", cc)
	}
	etag := resp.Header.Get("ETag")
	if etag == "" {
		t.Fatal("no ETag")
	}
	req, _ := http.NewRequest("GET", srv.URL+"/rcc.css", nil)
	req.Header.Set("If-None-Match", etag)
	resp2, err := http.DefaultClient.Do(req)
	if err != nil {
		t.Fatalf("conditional GET: %v", err)
	}
	resp2.Body.Close()
	if resp2.StatusCode != http.StatusNotModified {
		t.Errorf("conditional GET status=%d, want 304", resp2.StatusCode)
	}
}
