package platform

import (
	"net/http"
	"net/http/httptest"
	"strings"
	"testing"
)

const htmlDoc = "<html><head><title>cdrop</title></head><body></body></html>"

func htmlHandler() http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
		w.Header().Set("Content-Type", "text/html; charset=utf-8")
		w.WriteHeader(http.StatusOK)
		_, _ = w.Write([]byte(htmlDoc))
	})
}

func TestSessionInjectIntoDocument(t *testing.T) {
	session := &LoginResult{
		AccessToken:  "acc-token",
		RefreshToken: "ref-token",
		User:         UserInfo{ID: "u-1", Name: "alice"},
	}
	h := SessionInjectMiddleware(session, "my-host", "", "macos")(htmlHandler())

	rec := httptest.NewRecorder()
	h.ServeHTTP(rec, httptest.NewRequest(http.MethodGet, "/", nil))
	body := rec.Body.String()

	if !strings.Contains(body, "window.__CDROP_BOOT__") {
		t.Fatalf("boot global not injected:\n%s", body)
	}
	if !strings.Contains(body, `"access_token":"acc-token"`) {
		t.Errorf("access token not in injected payload:\n%s", body)
	}
	// Credential policy: the refresh_token must NEVER reach the page.
	if strings.Contains(body, "ref-token") || strings.Contains(body, "refresh_token") {
		t.Errorf("refresh_token must not be injected into the page:\n%s", body)
	}
	if !strings.Contains(body, `"device_name":"my-host"`) {
		t.Errorf("device name not in injected payload:\n%s", body)
	}
	if !strings.Contains(body, `"device_type":"macos"`) {
		t.Errorf("device type not in injected payload:\n%s", body)
	}
	// Injected before </head> so it runs before the deferred module bundle.
	if strings.Index(body, "__CDROP_BOOT__") > strings.Index(body, "</head>") {
		t.Error("injection must precede </head>")
	}
}

func TestSessionInjectPassesThroughNonDocument(t *testing.T) {
	apiBody := "streamed-api-body"
	next := http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
		_, _ = w.Write([]byte(apiBody))
	})
	h := SessionInjectMiddleware(&LoginResult{AccessToken: "x"}, "h", "", "")(next)

	rec := httptest.NewRecorder()
	h.ServeHTTP(rec, httptest.NewRequest(http.MethodGet, "/api/hub/events", nil))
	if rec.Body.String() != apiBody {
		t.Errorf("non-document request must pass through unchanged, got %q", rec.Body.String())
	}
}

func TestSessionInjectNilSessionStillInjectsDeviceBoot(t *testing.T) {
	// Logged out, the boot state is still injected — the page needs the device
	// name / api_base / device_type before login — but with a null session, and
	// no token of any kind leaks.
	h := SessionInjectMiddleware(nil, "my-host", "http://127.0.0.1:5000", "windows")(htmlHandler())
	rec := httptest.NewRecorder()
	h.ServeHTTP(rec, httptest.NewRequest(http.MethodGet, "/", nil))
	body := rec.Body.String()

	if !strings.Contains(body, "window.__CDROP_BOOT__") {
		t.Fatalf("boot global must be injected even when logged out:\n%s", body)
	}
	if !strings.Contains(body, `"session":null`) {
		t.Errorf("logged-out boot must carry a null session:\n%s", body)
	}
	if !strings.Contains(body, `"device_name":"my-host"`) ||
		!strings.Contains(body, `"device_type":"windows"`) ||
		!strings.Contains(body, `"api_base":"http://127.0.0.1:5000"`) {
		t.Errorf("device boot fields not injected:\n%s", body)
	}
	if strings.Contains(body, "access_token") {
		t.Errorf("no token must appear when logged out:\n%s", body)
	}
}

func TestSessionInjectEscapesScriptBreakout(t *testing.T) {
	// A display name containing </script> must not break out of the inline tag.
	session := &LoginResult{AccessToken: "acc", User: UserInfo{Name: "</script><b>x"}}
	h := SessionInjectMiddleware(session, "h", "", "")(htmlHandler())
	rec := httptest.NewRecorder()
	h.ServeHTTP(rec, httptest.NewRequest(http.MethodGet, "/", nil))
	body := rec.Body.String()

	// The literal closing tag from the name must NOT appear verbatim; encoding/json
	// HTML-escapes < / > to < / > by default.
	if strings.Contains(body, "</script><b>x") {
		t.Errorf("script breakout not escaped:\n%s", body)
	}
	if !strings.Contains(body, `</script>`) {
		t.Errorf("expected HTML-escaped name in payload:\n%s", body)
	}
}
