package api

import (
	"context"
	"encoding/json"
	"errors"
	"net/http"
	"net/http/httptest"
	"testing"
	"time"
)

func TestNewHealthHandler(t *testing.T) {
	tests := []struct {
		name       string
		config     HealthConfig
		wantStatus int
		wantHealth string
	}{
		{
			name: "all checks healthy",
			config: HealthConfig{
				Service: "test-service",
				Timeout: 5 * time.Second,
				Checks: map[string]HealthChecker{
					"db": func(ctx context.Context) error {
						return nil
					},
					"cache": func(ctx context.Context) error {
						return nil
					},
				},
			},
			wantStatus: http.StatusOK,
			wantHealth: "healthy",
		},
		{
			name: "one check unhealthy",
			config: HealthConfig{
				Service: "test-service",
				Timeout: 5 * time.Second,
				Checks: map[string]HealthChecker{
					"db": func(ctx context.Context) error {
						return nil
					},
					"cache": func(ctx context.Context) error {
						return errors.New("connection refused")
					},
				},
			},
			wantStatus: http.StatusServiceUnavailable,
			wantHealth: "unhealthy",
		},
		{
			name: "no checks configured",
			config: HealthConfig{
				Service: "test-service",
			},
			wantStatus: http.StatusOK,
			wantHealth: "healthy",
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			handler := NewHealthHandler(tt.config)

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

			handler(rec, req)

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

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

			data, ok := resp.Data.(map[string]any)
			if !ok {
				t.Fatalf("expected map in data, got %T", resp.Data)
			}

			if status := data["status"]; status != tt.wantHealth {
				t.Errorf("health status = %q, want %q", status, tt.wantHealth)
			}

			if service := data["service"]; service != tt.config.Service {
				t.Errorf("service = %q, want %q", service, tt.config.Service)
			}
		})
	}
}

func TestNewHealthHandler_Timeout(t *testing.T) {
	config := HealthConfig{
		Service: "test-service",
		Timeout: 100 * time.Millisecond,
		Checks: map[string]HealthChecker{
			"slow": func(ctx context.Context) error {
				select {
				case <-time.After(1 * time.Second):
					return nil
				case <-ctx.Done():
					return ctx.Err()
				}
			},
		},
	}

	handler := NewHealthHandler(config)

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

	start := time.Now()
	handler(rec, req)
	duration := time.Since(start)

	// Should return before the full second
	if duration > 500*time.Millisecond {
		t.Errorf("took %v, expected less than 500ms", duration)
	}

	// Should be unhealthy due to timeout
	if rec.Code != http.StatusServiceUnavailable {
		t.Errorf("status = %d, want %d", rec.Code, http.StatusServiceUnavailable)
	}
}

func TestNewLivenessHandler(t *testing.T) {
	handler := NewLivenessHandler("test-service")

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

	handler(rec, req)

	if rec.Code != http.StatusOK {
		t.Errorf("status = %d, want %d", rec.Code, http.StatusOK)
	}

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

	data, ok := resp.Data.(map[string]any)
	if !ok {
		t.Fatalf("expected map in data, got %T", resp.Data)
	}

	if status := data["status"]; status != "ok" {
		t.Errorf("status = %q, want %q", status, "ok")
	}

	if service := data["service"]; service != "test-service" {
		t.Errorf("service = %q, want %q", service, "test-service")
	}
}

func TestPingChecker(t *testing.T) {
	t.Run("healthy ping", func(t *testing.T) {
		pingFn := func(ctx context.Context) error {
			return nil
		}

		checker := PingChecker(pingFn)
		err := checker(context.Background())

		if err != nil {
			t.Errorf("expected nil, got %v", err)
		}
	})

	t.Run("unhealthy ping", func(t *testing.T) {
		pingErr := errors.New("connection refused")
		pingFn := func(ctx context.Context) error {
			return pingErr
		}

		checker := PingChecker(pingFn)
		err := checker(context.Background())

		if err != pingErr {
			t.Errorf("expected %v, got %v", pingErr, err)
		}
	})
}

func TestHTTPChecker(t *testing.T) {
	t.Run("healthy endpoint", func(t *testing.T) {
		server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
			w.WriteHeader(http.StatusOK)
		}))
		defer server.Close()

		checker := HTTPChecker(server.URL)
		err := checker(context.Background())

		if err != nil {
			t.Errorf("expected nil, got %v", err)
		}
	})

	t.Run("unhealthy endpoint", func(t *testing.T) {
		server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
			w.WriteHeader(http.StatusServiceUnavailable)
		}))
		defer server.Close()

		checker := HTTPChecker(server.URL)
		err := checker(context.Background())

		if err == nil {
			t.Error("expected error, got nil")
		}
	})

	t.Run("connection refused", func(t *testing.T) {
		checker := HTTPChecker("http://localhost:99999")
		err := checker(context.Background())

		if err == nil {
			t.Error("expected error, got nil")
		}
	})
}
