package observability

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

func TestHealthChecker_SetReady(t *testing.T) {
	tests := []struct {
		name     string
		setReady bool
		want     bool
	}{
		{
			name:     "set ready to true",
			setReady: true,
			want:     true,
		},
		{
			name:     "set ready to false",
			setReady: false,
			want:     false,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			h := NewHealthChecker()
			h.SetReady(tt.setReady)

			if got := h.IsReady(); got != tt.want {
				t.Errorf("IsReady() = %v, want %v", got, tt.want)
			}
		})
	}
}

func TestHealthChecker_LivenessHandler(t *testing.T) {
	tests := []struct {
		name           string
		ready          bool
		wantStatusCode int
		wantStatus     string
	}{
		{
			name:           "liveness always ok when ready",
			ready:          true,
			wantStatusCode: http.StatusOK,
			wantStatus:     "ok",
		},
		{
			name:           "liveness always ok when not ready",
			ready:          false,
			wantStatusCode: http.StatusOK,
			wantStatus:     "ok",
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			h := NewHealthChecker()
			h.SetReady(tt.ready)

			req := httptest.NewRequest(http.MethodGet, "/healthz", nil)
			rec := httptest.NewRecorder()

			h.LivenessHandler()(rec, req)

			if rec.Code != tt.wantStatusCode {
				t.Errorf("status code = %d, want %d", rec.Code, tt.wantStatusCode)
			}

			var resp HealthStatus
			if err := json.NewDecoder(rec.Body).Decode(&resp); err != nil {
				t.Fatalf("failed to decode response: %v", err)
			}

			if resp.Status != tt.wantStatus {
				t.Errorf("status = %q, want %q", resp.Status, tt.wantStatus)
			}
		})
	}
}

func TestHealthChecker_ReadinessHandler(t *testing.T) {
	tests := []struct {
		name           string
		ready          bool
		wantStatusCode int
		wantStatus     string
	}{
		{
			name:           "readiness ok when ready",
			ready:          true,
			wantStatusCode: http.StatusOK,
			wantStatus:     "ready",
		},
		{
			name:           "readiness not ok when not ready",
			ready:          false,
			wantStatusCode: http.StatusServiceUnavailable,
			wantStatus:     "not ready",
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			h := NewHealthChecker()
			h.SetReady(tt.ready)

			req := httptest.NewRequest(http.MethodGet, "/readyz", nil)
			rec := httptest.NewRecorder()

			h.ReadinessHandler()(rec, req)

			if rec.Code != tt.wantStatusCode {
				t.Errorf("status code = %d, want %d", rec.Code, tt.wantStatusCode)
			}

			var resp HealthStatus
			if err := json.NewDecoder(rec.Body).Decode(&resp); err != nil {
				t.Fatalf("failed to decode response: %v", err)
			}

			if resp.Status != tt.wantStatus {
				t.Errorf("status = %q, want %q", resp.Status, tt.wantStatus)
			}
		})
	}
}

func TestStatusToString(t *testing.T) {
	tests := []struct {
		name   string
		status int
		want   string
	}{
		{name: "200 OK", status: 200, want: "2xx"},
		{name: "201 Created", status: 201, want: "2xx"},
		{name: "299 edge case", status: 299, want: "2xx"},
		{name: "301 Redirect", status: 301, want: "3xx"},
		{name: "400 Bad Request", status: 400, want: "4xx"},
		{name: "404 Not Found", status: 404, want: "4xx"},
		{name: "500 Internal Error", status: 500, want: "5xx"},
		{name: "502 Bad Gateway", status: 502, want: "5xx"},
		{name: "100 Continue", status: 100, want: "unknown"},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			got := statusToString(tt.status)
			if got != tt.want {
				t.Errorf("statusToString(%d) = %q, want %q", tt.status, got, tt.want)
			}
		})
	}
}
