package processhttp

import (
	"encoding/json"
	"net/http"
	"net/http/httptest"
	"reflect"
	"testing"
)

func TestOperationalSurfaceOwnsStableProcessFacts(t *testing.T) {
	ready := true
	capabilities := []string{"z", "a"}
	handler, err := NewHandler(Config{
		Role: "runtime-api", Version: "sha-1",
		Live: func() bool { return true }, Ready: func() bool { return ready },
		Capabilities: capabilities,
	})
	if err != nil {
		t.Fatal(err)
	}

	assertStatus(t, handler, LivePath, http.StatusOK)
	assertStatus(t, handler, ReadyPath, http.StatusOK)
	ready = false
	assertStatus(t, handler, ReadyPath, http.StatusServiceUnavailable)

	version := getJSON[map[string]string](t, handler, VersionPath)
	if version["role"] != "runtime-api" || version["version"] != "sha-1" {
		t.Fatalf("version = %#v", version)
	}
	capabilityBody := getJSON[struct {
		Role         string   `json:"role"`
		Capabilities []string `json:"capabilities"`
	}](t, handler, CapabilitiesPath)
	if capabilityBody.Role != "runtime-api" || !reflect.DeepEqual(capabilityBody.Capabilities, []string{"a", "z"}) {
		t.Fatalf("capabilities = %#v", capabilityBody)
	}
	if !reflect.DeepEqual(capabilities, []string{"z", "a"}) {
		t.Fatalf("handler mutated caller capabilities: %#v", capabilities)
	}
}

func TestNilProbeFailsClosed(t *testing.T) {
	handler, err := NewHandler(Config{Role: "runtime-worker", Version: "sha-1"})
	if err != nil {
		t.Fatal(err)
	}
	assertStatus(t, handler, LivePath, http.StatusServiceUnavailable)
	assertStatus(t, handler, ReadyPath, http.StatusServiceUnavailable)
	capabilityBody := getJSON[struct {
		Capabilities []string `json:"capabilities"`
	}](t, handler, CapabilitiesPath)
	if capabilityBody.Capabilities == nil || len(capabilityBody.Capabilities) != 0 {
		t.Fatalf("empty capabilities = %#v, want non-nil empty array", capabilityBody.Capabilities)
	}
}

func TestConfigurationAndMethodsFailClosed(t *testing.T) {
	for _, config := range []Config{
		{},
		{Role: "runtime-api", Capabilities: []string{"read"}},
		{Version: "sha-1", Capabilities: []string{"read"}},
		{Role: " runtime-api", Version: "sha-1"},
		{Role: "runtime-api", Version: "sha-1 "},
		{Role: "runtime-api", Version: "sha-1", Capabilities: []string{""}},
		{Role: "runtime-api", Version: "sha-1", Capabilities: []string{"read", "read"}},
	} {
		if handler, err := NewHandler(config); err == nil || handler != nil {
			t.Fatalf("NewHandler(%+v) = %#v, %v; want rejected", config, handler, err)
		}
	}
	handler, err := NewHandler(Config{Role: "runtime-api", Version: "sha-1", Capabilities: []string{"read"}})
	if err != nil {
		t.Fatal(err)
	}
	for _, path := range []string{LivePath, ReadyPath, VersionPath, CapabilitiesPath} {
		response := httptest.NewRecorder()
		handler.ServeHTTP(response, httptest.NewRequest(http.MethodPost, path, nil))
		if response.Code != http.StatusMethodNotAllowed || response.Header().Get("Allow") != "GET, HEAD" {
			t.Fatalf("POST %s status=%d allow=%q", path, response.Code, response.Header().Get("Allow"))
		}
	}
}

func assertStatus(t *testing.T, handler http.Handler, path string, want int) {
	t.Helper()
	response := httptest.NewRecorder()
	handler.ServeHTTP(response, httptest.NewRequest(http.MethodGet, path, nil))
	if response.Code != want {
		t.Fatalf("GET %s status = %d, want %d; body=%s", path, response.Code, want, response.Body.String())
	}
}

func getJSON[T any](t *testing.T, handler http.Handler, path string) T {
	t.Helper()
	response := httptest.NewRecorder()
	handler.ServeHTTP(response, httptest.NewRequest(http.MethodGet, path, nil))
	if response.Code != http.StatusOK {
		t.Fatalf("GET %s status = %d; body=%s", path, response.Code, response.Body.String())
	}
	var value T
	if err := json.Unmarshal(response.Body.Bytes(), &value); err != nil {
		t.Fatal(err)
	}
	return value
}
