package telemetry

import (
	"context"
	"net/http"
	"net/http/httptest"
	"os"
	"testing"
	"time"

	"github.com/orchard9/rdev/internal/envutil"
)

func TestDefaultConfig(t *testing.T) {
	// Clear env vars that might affect defaults
	_ = os.Unsetenv("OTEL_EXPORTER_OTLP_ENDPOINT")
	_ = os.Unsetenv("OTEL_SERVICE_NAME")
	_ = os.Unsetenv("OTEL_SERVICE_VERSION")
	_ = os.Unsetenv("OTEL_SERVICE_NAMESPACE")
	_ = os.Unsetenv("OTEL_ENABLED")

	cfg := DefaultConfig()

	if cfg.Endpoint != "otel-collector.observability.svc.cluster.local:4317" {
		t.Errorf("expected default endpoint, got %s", cfg.Endpoint)
	}
	if cfg.ServiceName != "rdev-api" {
		t.Errorf("expected default service name, got %s", cfg.ServiceName)
	}
	if cfg.ServiceVersion != "unknown" {
		t.Errorf("expected default service version, got %s", cfg.ServiceVersion)
	}
	if cfg.ServiceNamespace != "rdev" {
		t.Errorf("expected default service namespace, got %s", cfg.ServiceNamespace)
	}
	if !cfg.Enabled {
		t.Error("expected telemetry enabled by default")
	}
}

func TestDefaultConfigWithEnv(t *testing.T) {
	// Set custom env vars
	_ = os.Setenv("OTEL_EXPORTER_OTLP_ENDPOINT", "custom-collector:4317")
	_ = os.Setenv("OTEL_SERVICE_NAME", "custom-service")
	_ = os.Setenv("OTEL_SERVICE_VERSION", "v1.2.3")
	_ = os.Setenv("OTEL_SERVICE_NAMESPACE", "custom-ns")
	_ = os.Setenv("OTEL_ENABLED", "false")
	defer func() {
		_ = os.Unsetenv("OTEL_EXPORTER_OTLP_ENDPOINT")
		_ = os.Unsetenv("OTEL_SERVICE_NAME")
		_ = os.Unsetenv("OTEL_SERVICE_VERSION")
		_ = os.Unsetenv("OTEL_SERVICE_NAMESPACE")
		_ = os.Unsetenv("OTEL_ENABLED")
	}()

	cfg := DefaultConfig()

	if cfg.Endpoint != "custom-collector:4317" {
		t.Errorf("expected custom endpoint, got %s", cfg.Endpoint)
	}
	if cfg.ServiceName != "custom-service" {
		t.Errorf("expected custom service name, got %s", cfg.ServiceName)
	}
	if cfg.ServiceVersion != "v1.2.3" {
		t.Errorf("expected custom service version, got %s", cfg.ServiceVersion)
	}
	if cfg.ServiceNamespace != "custom-ns" {
		t.Errorf("expected custom service namespace, got %s", cfg.ServiceNamespace)
	}
	if cfg.Enabled {
		t.Error("expected telemetry disabled")
	}
}

func TestNewTelemetryDisabled(t *testing.T) {
	cfg := Config{
		Enabled:     false,
		ServiceName: "test-service",
	}

	tel, err := New(context.Background(), cfg)
	if err != nil {
		t.Fatalf("unexpected error: %v", err)
	}

	if tel == nil {
		t.Fatal("expected telemetry instance")
	}

	// Verify tracer is available (noop)
	if tel.Tracer() == nil {
		t.Error("expected noop tracer")
	}

	// Shutdown should be safe
	if err := tel.Shutdown(context.Background()); err != nil {
		t.Errorf("unexpected shutdown error: %v", err)
	}
}

func TestNewTelemetryWithBadEndpoint(t *testing.T) {
	// This test verifies that creation doesn't fail even with unreachable endpoint
	// The actual connection happens asynchronously during export
	cfg := Config{
		Enabled:     true,
		Endpoint:    "localhost:99999",
		ServiceName: "test-service",
		Insecure:    true,
	}

	ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
	defer cancel()

	tel, err := New(ctx, cfg)
	if err != nil {
		t.Fatalf("unexpected error creating telemetry: %v", err)
	}
	defer func() { _ = tel.Shutdown(context.Background()) }()

	// Should be able to create spans even if collector is unreachable
	_, span := tel.StartSpan(context.Background(), "test-span")
	span.End()
}

func TestStartSpan(t *testing.T) {
	cfg := Config{
		Enabled:     false,
		ServiceName: "test-service",
	}

	tel, err := New(context.Background(), cfg)
	if err != nil {
		t.Fatalf("unexpected error: %v", err)
	}
	defer func() { _ = tel.Shutdown(context.Background()) }()

	ctx, span := tel.StartSpan(context.Background(), "test-operation")
	if span == nil {
		t.Error("expected span")
	}
	if ctx == nil {
		t.Error("expected context")
	}
	span.End()
}

func TestGetEnvBool(t *testing.T) {
	tests := []struct {
		value    string
		expected bool
	}{
		{"true", true},
		{"TRUE", true},
		{"True", true},
		{"1", true},
		{"yes", true},
		{"YES", true},
		{"false", false},
		{"FALSE", false},
		{"0", false},
		{"no", false},
		{"anything", false},
	}

	for _, tt := range tests {
		t.Run(tt.value, func(t *testing.T) {
			os.Setenv("TEST_BOOL", tt.value)
			defer os.Unsetenv("TEST_BOOL")

			result := envutil.GetEnvBool("TEST_BOOL", false)
			if result != tt.expected {
				t.Errorf("GetEnvBool(%q) = %v, want %v", tt.value, result, tt.expected)
			}
		})
	}
}

func TestNormalizePath(t *testing.T) {
	tests := []struct {
		input    string
		expected string
	}{
		// Keys
		{"/keys/550e8400-e29b-41d4-a716-446655440000", "/keys/{id}"},
		{"/keys", "/keys"},

		// Projects
		{"/projects/pantheon", "/projects/{id}"},
		{"/projects/pantheon/claude", "/projects/{id}/claude"},
		{"/projects/aeries/shell", "/projects/{id}/shell"},
		{"/projects/test-123/events", "/projects/{id}/events"},

		// Claude config
		{"/projects/pantheon/claude-config/commands/deploy", "/projects/{id}/claude-config/commands/{name}"},
		{"/projects/pantheon/claude-config/skills/go-testing", "/projects/{id}/claude-config/skills/{name}"},
		{"/projects/pantheon/claude-config/agents/reviewer", "/projects/{id}/claude-config/agents/{name}"},
		{"/projects/pantheon/claude-config/commands", "/projects/{id}/claude-config/commands"},
		{"/projects/pantheon/claude-config", "/projects/{id}/claude-config"},

		// Unchanged
		{"/health", "/health"},
		{"/ready", "/ready"},
		{"/metrics", "/metrics"},
		{"/docs", "/docs"},
	}

	for _, tt := range tests {
		t.Run(tt.input, func(t *testing.T) {
			result := normalizePath(tt.input)
			if result != tt.expected {
				t.Errorf("normalizePath(%q) = %q, want %q", tt.input, result, tt.expected)
			}
		})
	}
}

func TestMiddleware(t *testing.T) {
	// Create a simple handler
	handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		w.WriteHeader(http.StatusOK)
		_, _ = w.Write([]byte("OK"))
	})

	// Wrap with telemetry middleware
	wrapped := Middleware("test-service")(handler)

	// Create test request
	req := httptest.NewRequest(http.MethodGet, "/health", nil)
	req.Header.Set("X-Real-IP", "192.168.1.1")
	rec := httptest.NewRecorder()

	// Execute
	wrapped.ServeHTTP(rec, req)

	// Verify response
	if rec.Code != http.StatusOK {
		t.Errorf("expected status 200, got %d", rec.Code)
	}
	if rec.Body.String() != "OK" {
		t.Errorf("expected body OK, got %s", rec.Body.String())
	}
}

func TestMiddlewareWithError(t *testing.T) {
	// Create a handler that returns an error
	handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		w.WriteHeader(http.StatusInternalServerError)
		_, _ = w.Write([]byte("error"))
	})

	wrapped := Middleware("test-service")(handler)

	req := httptest.NewRequest(http.MethodPost, "/projects/test/claude", nil)
	rec := httptest.NewRecorder()

	wrapped.ServeHTTP(rec, req)

	if rec.Code != http.StatusInternalServerError {
		t.Errorf("expected status 500, got %d", rec.Code)
	}
}

func TestResponseWriter(t *testing.T) {
	rec := httptest.NewRecorder()
	rw := &responseWriter{ResponseWriter: rec, statusCode: http.StatusOK}

	// Test WriteHeader
	rw.WriteHeader(http.StatusCreated)
	if rw.statusCode != http.StatusCreated {
		t.Errorf("expected status 201, got %d", rw.statusCode)
	}

	// Test Write
	n, err := rw.Write([]byte("test"))
	if err != nil {
		t.Errorf("unexpected error: %v", err)
	}
	if n != 4 {
		t.Errorf("expected 4 bytes written, got %d", n)
	}
	if rw.bytesWritten != 4 {
		t.Errorf("expected 4 bytes tracked, got %d", rw.bytesWritten)
	}

	// Test Unwrap
	if rw.Unwrap() != rec {
		t.Error("Unwrap should return original ResponseWriter")
	}
}

func TestGetScheme(t *testing.T) {
	tests := []struct {
		name     string
		setup    func(*http.Request)
		expected string
	}{
		{
			name:     "default http",
			setup:    func(r *http.Request) {},
			expected: "http",
		},
		{
			name: "x-forwarded-proto https",
			setup: func(r *http.Request) {
				r.Header.Set("X-Forwarded-Proto", "https")
			},
			expected: "https",
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			req := httptest.NewRequest(http.MethodGet, "/test", nil)
			tt.setup(req)
			result := getScheme(req)
			if result != tt.expected {
				t.Errorf("getScheme() = %q, want %q", result, tt.expected)
			}
		})
	}
}
