package web

// Group A (hub v0.47.0 UI reorganization) — the customer page's client-side hash tabs.
// Graceful degradation is load-bearing: panels are hidden ONLY by a JS-added body class,
// so the no-JS render (what these tests see) must still contain EVERY section's markup.

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

	"gitea.dooplex.hu/admin/felhom-hub/internal/store"
)

// A controller-report body exercising every Overview/Applications section.
const tabsTestReportJSON = `{
	"health": {"status": "ok", "warnings": ["disk filling"]},
	"system": {"hostname": "felhom-box", "os": "Debian 12", "kernel": "6.8", "cpu_model": "N100", "cpu_cores": 4},
	"storage": [{"mount": "/", "percent": 41.0, "used_gb": 12.3, "total_gb": 30.0}],
	"containers": {"running": 3, "total": 4, "list": [{"name": "vaultwarden", "state": "running", "cpu_percent": 1.2, "memory_mb": 90}]},
	"backup": {"enabled": true, "snapshot_count": 7, "repo_size_mb": 1200, "integrity_ok": true},
	"geo_restriction": {"enabled": true, "allowed_countries": ["HU"]}
}`

func renderCustomerPage(t *testing.T, s *Server, customerID string) string {
	t.Helper()
	rr := httptest.NewRecorder()
	s.handleCustomerUnified(rr, httptest.NewRequest("GET", "/customers/"+customerID, nil), customerID)
	if rr.Code != 200 {
		t.Fatalf("customer page status = %d", rr.Code)
	}
	return rr.Body.String()
}

func TestTemplates_CustomerTabs(t *testing.T) {
	s, st := newTestServer(t)
	if err := st.SaveCustomerConfig(&store.CustomerConfig{
		CustomerID: "acme", CustomerName: "Acme", Domain: "acme.hu",
		RetrievalPassword: "pw", APIKey: "k", Status: "active",
	}); err != nil {
		t.Fatal(err)
	}
	if err := st.SaveReport("acme", []byte(tabsTestReportJSON)); err != nil {
		t.Fatal(err)
	}
	// One error event → the Events tab label carries the red count badge.
	if _, err := st.SaveEvent("acme", "app_crash_loop", "error", "vaultwarden restarting", "{}", "controller"); err != nil {
		t.Fatal(err)
	}

	html := renderCustomerPage(t, s, "acme")

	// The tab nav renders all 8 tabs (hash links — plain anchors without JS).
	// v0.48.0 edit-a: "settings" became "edit" (the standalone config form merged in).
	for _, tab := range []string{
		"overview", "applications", "setup", "edit", "backup", "events", "notifications", "host",
	} {
		if !strings.Contains(html, `href="#tab=`+tab+`"`) {
			t.Errorf("tab nav missing tab %q", tab)
		}
	}

	// No-JS completeness: every section's markup is still in the body (hiding is done by a
	// JS-added body class — the server render carries everything).
	for _, want := range []string{
		"Customer Info", "Health", "System", "Storage", "Backup",
		"Containers (3/4)", "vaultwarden",
		"Credentials", "Setup Command", "YAML Preview",
		"Controller Update", "Geo-korlátozás",
		"Events", "Notifications", "Report History",
	} {
		if !strings.Contains(html, want) {
			t.Errorf("no-JS body missing section content %q", want)
		}
	}

	// Sticky summary strip with the always-visible vitals.
	if !strings.Contains(html, `class="summary-strip"`) {
		t.Error("summary strip missing")
	}

	// Events tab badge: 1 error event → a red count on the tab label.
	if !strings.Contains(html, `<span class="tab-badge">1</span>`) {
		t.Error("Events tab badge missing despite 1 error event")
	}

	// Panels are hidden only via the js-tabs body class — no inline hiding in the markup.
	if strings.Contains(html, `tab-panel" data-tab="events" style=`) {
		t.Error("tab panel carries inline style — hiding must be class-driven")
	}
}

// Group B — the Host tab renders the SHARED host_detail_body sub-template: the same body
// the standalone /hosts/{id} page renders (one instance per host; a list by design).
func TestTemplates_CustomerHostTab(t *testing.T) {
	s, st := newTestServer(t)
	if err := st.SaveCustomerConfig(&store.CustomerConfig{
		CustomerID: "c1", CustomerName: "Acme", RetrievalPassword: "pw", APIKey: "k", Status: "active",
	}); err != nil {
		t.Fatal(err)
	}
	if err := st.UpsertHost(&store.Host{HostID: "demo-felhom-01", CustomerID: "c1", APIKey: "host-key-secret"}); err != nil {
		t.Fatal(err)
	}
	if err := st.UpsertGuestFromReport(&store.Guest{GuestID: store.GuestID("demo-felhom-01", 9201),
		CustomerID: "c1", HostID: "demo-felhom-01", VMID: 9201, DisplayName: "acme", Status: "running",
		ControllerVersion: "0.87.0"}); err != nil {
		t.Fatal(err)
	}
	if err := st.SaveHostReport("demo-felhom-01", "c1", []byte(testReportJSON), store.HostReportDenorm{
		AgentVersion: "0.43.0", CloudflaredStatus: "healthy"}); err != nil {
		t.Fatal(err)
	}

	html := renderCustomerPage(t, s, "c1")

	// The Host tab panel carries the host_detail_body content.
	for _, want := range []string{
		"demo-felhom-01",  // host identity
		"Storage Targets", // shared body section
		"felhom-usb",      // storage target from the report
		">9201<",          // guest vmid
		`href="/hosts/demo-felhom-01"`, // cross-link to the standalone page
	} {
		if !strings.Contains(html, want) {
			t.Errorf("Host tab missing %q", want)
		}
	}
	// The two request-logs forms with a CSRF field each.
	if got := strings.Count(html, `action="/hosts/demo-felhom-01/request-logs"`); got != 2 {
		t.Errorf("Host tab has %d request-logs forms, want 2", got)
	}
	if !strings.Contains(html, `name="_csrf"`) {
		t.Error("request-logs forms missing the CSRF field")
	}
	// SECURITY: the host api_key must never reach the customer page either.
	if strings.Contains(html, "host-key-secret") {
		t.Error("SECRET LEAK: customer page rendered the host api_key")
	}

	// /hosts/{id} renders the IDENTICAL body (same sub-template) — one shared marker that
	// only host_detail_body emits must appear in both renders.
	rr := httptest.NewRecorder()
	s.handleHostDetail(rr, httptest.NewRequest("GET", "/hosts/demo-felhom-01", nil), "demo-felhom-01")
	hostPage := rr.Body.String()
	const marker = "Diagnostics — Log Bundles"
	if !strings.Contains(html, marker) || !strings.Contains(hostPage, marker) {
		t.Errorf("shared host_detail_body marker %q missing from one of the surfaces", marker)
	}
}

func TestTemplates_CustomerHostTab_Empty(t *testing.T) {
	s, st := newTestServer(t)
	if err := st.SaveCustomerConfig(&store.CustomerConfig{
		CustomerID: "c2", CustomerName: "NoHost", RetrievalPassword: "pw", APIKey: "k", Status: "active",
	}); err != nil {
		t.Fatal(err)
	}
	html := renderCustomerPage(t, s, "c2")
	if !strings.Contains(html, "No host enrolled yet.") {
		t.Error("hostless customer must show the Host tab empty state")
	}
}

// A customer with a config but no reports keeps the waiting banner ABOVE the tab bar
// (always visible) and shows no Events badge.
func TestTemplates_CustomerTabs_NoReports(t *testing.T) {
	s, st := newTestServer(t)
	if err := st.SaveCustomerConfig(&store.CustomerConfig{
		CustomerID: "fresh", CustomerName: "Fresh", RetrievalPassword: "pw", APIKey: "k", Status: "active",
	}); err != nil {
		t.Fatal(err)
	}
	html := renderCustomerPage(t, s, "fresh")

	banner := strings.Index(html, "Waiting for First Report")
	nav := strings.Index(html, `id="tab-nav"`)
	if banner < 0 {
		t.Fatal("waiting banner missing")
	}
	if nav < 0 {
		t.Fatal("tab nav missing")
	}
	if banner > nav {
		t.Error("waiting banner renders BELOW the tab bar — must stay above (always visible)")
	}
	if strings.Contains(html, `class="tab-badge"`) {
		t.Error("Events badge rendered with no events")
	}
}
