package main

// Regression test for the 2026-05-31 ERR_TOO_MANY_REDIRECTS loop.
//
// The "new UI" overhaul added a rootPanel handler that selected which
// front-end ("/" -> index.html | new.html) to serve based on the panel_ui
// cookie. The first implementation rewrote r.URL.Path to "/index.html" and
// delegated to http.FileServer, which CANONICALIZES any ".../index.html"
// path with a 301 redirect to "./" — turning an authenticated GET "/" into
// an infinite /->index.html(301)->/ loop. Unauthenticated requests 302'd to
// /login before reaching rootPanel, which is why server-side curl looked
// clean and only logged-in users looped.
//
// This test wires the REAL embedded static FS through the SAME rootPanel
// logic as production, gates it behind a fake-authenticated requireSession,
// and drives it with a redirect-following client (like a browser). It must
// return 200 with no redirect for both UI modes.

import (
	"io"
	"io/fs"
	"net/http"
	"net/http/httptest"
	"net/http/cookiejar"
	"net/url"
	"strings"
	"testing"
	"time"
)

// buildRootPanel reproduces the production handler() wiring for the root
// route verbatim (kept in sync with httpapi.go). If you change rootPanel in
// httpapi.go, mirror it here.
func buildRootPanel(t *testing.T) http.Handler {
	t.Helper()
	sub, err := fs.Sub(staticFS, "static")
	if err != nil {
		t.Fatalf("fs.Sub: %v", err)
	}
	rootPanel := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		// Mirror prod (httpapi.go): new.html is the DEFAULT; classic is opt-in.
		file := "new.html"
		if c, err := r.Cookie("panel_ui"); err == nil && c.Value == "classic" {
			file = "index.html"
		}
		f, err := sub.Open(file)
		if err != nil {
			alt := "index.html"
			if file == "index.html" {
				alt = "new.html"
			}
			if f, err = sub.Open(alt); err != nil {
				http.Error(w, "panel UI asset missing", http.StatusInternalServerError)
				return
			}
			file = alt
		}
		defer f.Close()
		seeker, ok := f.(io.ReadSeeker)
		if !ok {
			w.Header().Set("Content-Type", "text/html; charset=utf-8")
			_, _ = io.Copy(w, f)
			return
		}
		http.ServeContent(w, r, file, time.Time{}, seeker)
	})

	// Fake "authenticated" requireSession: always lets the request through,
	// exactly as the real one does once a valid session cookie is present.
	mux := http.NewServeMux()
	mux.Handle("GET /{$}", rootPanel)
	mux.Handle("/", http.FileServer(http.FS(sub))) // catch-all, like prod
	return mux
}

func TestRootPanelNoRedirectLoop(t *testing.T) {
	srv := httptest.NewServer(buildRootPanel(t))
	defer srv.Close()

	cases := []struct {
		name      string
		cookieVal string // "" => no panel_ui cookie
		wantNeedle string
	}{
		{"default (no cookie) -> new", "", "Refuge"},
		{"classic opt-in", "classic", "Refuge"},
		{"new explicit", "new", "Refuge"},
		{"garbage cookie -> new (default)", "lolwut", "Refuge"},
	}

	for _, tc := range cases {
		t.Run(tc.name, func(t *testing.T) {
			jar, _ := cookiejar.New(nil)
			u, _ := url.Parse(srv.URL)
			if tc.cookieVal != "" {
				jar.SetCookies(u, []*http.Cookie{{Name: "panel_ui", Value: tc.cookieVal}})
			}
			// Cap redirects low so a loop fails fast instead of hanging.
			client := &http.Client{
				Jar: jar,
				CheckRedirect: func(req *http.Request, via []*http.Request) error {
					if len(via) >= 5 {
						return http.ErrUseLastResponse
					}
					return nil
				},
			}
			resp, err := client.Get(srv.URL + "/")
			if err != nil {
				t.Fatalf("GET /: %v", err)
			}
			defer resp.Body.Close()
			body, _ := io.ReadAll(io.LimitReader(resp.Body, 1<<20))

			if resp.StatusCode != http.StatusOK {
				t.Fatalf("status = %d (want 200); Location=%q — a non-200 here means the redirect loop is back",
					resp.StatusCode, resp.Header.Get("Location"))
			}
			if loc := resp.Header.Get("Location"); loc != "" {
				t.Fatalf("got a Location header %q on the final response — root must serve, never redirect", loc)
			}
			if !strings.Contains(string(body), tc.wantNeedle) {
				t.Fatalf("served body missing %q (got %d bytes) — wrong/empty UI served", tc.wantNeedle, len(body))
			}
			ct := resp.Header.Get("Content-Type")
			if !strings.HasPrefix(ct, "text/html") {
				t.Fatalf("Content-Type = %q (want text/html)", ct)
			}
		})
	}
}

// TestRootPanelServesDistinctUIs proves the cookie actually toggles between
// the two distinct front-ends (not just that both return 200).
func TestRootPanelServesDistinctUIs(t *testing.T) {
	srv := httptest.NewServer(buildRootPanel(t))
	defer srv.Close()

	get := func(cookie string) string {
		jar, _ := cookiejar.New(nil)
		u, _ := url.Parse(srv.URL)
		if cookie != "" {
			jar.SetCookies(u, []*http.Cookie{{Name: "panel_ui", Value: cookie}})
		}
		client := &http.Client{Jar: jar}
		resp, err := client.Get(srv.URL + "/")
		if err != nil {
			t.Fatalf("GET: %v", err)
		}
		defer resp.Body.Close()
		b, _ := io.ReadAll(resp.Body)
		return string(b)
	}

	classic := get("classic")  // opt-in classic
	def := get("")             // no cookie → DEFAULT (new)

	// The new UI is branded "Refuge Command Center"; classic is not.
	// As of 2026-05-31 the new UI is the default (no cookie → new).
	if !strings.Contains(def, "Command Center") {
		t.Errorf("default (no cookie) body missing 'Command Center' brand — new.html is supposed to be the default")
	}
	if strings.Contains(classic, "Command Center") {
		t.Errorf("panel_ui=classic served the new-brand UI — classic opt-out not honored")
	}
	if classic == def {
		t.Errorf("classic and default served identical bytes — cookie toggle not switching front-ends")
	}
}
