package server

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

	"git.dcentral.systems/toolz/goplt/internal/health"
	"git.dcentral.systems/toolz/goplt/internal/metrics"
	"github.com/gin-gonic/gin"
	"go.opentelemetry.io/otel/trace/noop"
)

func TestNewServer(t *testing.T) {
	gin.SetMode(gin.TestMode)

	mockConfig := &mockConfigProvider{
		values: map[string]any{
			"environment":          "test",
			"server.port":          8080,
			"server.host":          "127.0.0.1",
			"server.read_timeout":  "30s",
			"server.write_timeout": "30s",
		},
	}

	mockLogger := &mockLogger{}
	healthRegistry := health.NewRegistry()
	metricsRegistry := metrics.NewMetrics()
	mockErrorBus := &mockErrorBusServer{}
	tracer := noop.NewTracerProvider()

	srv, err := NewServer(mockConfig, mockLogger, healthRegistry, metricsRegistry, mockErrorBus, tracer)
	if err != nil {
		t.Fatalf("Failed to create server: %v", err)
	}

	if srv == nil {
		t.Fatal("Expected server, got nil")
	}

	if srv.httpServer == nil {
		t.Error("Expected http server, got nil")
	}

	if srv.router == nil {
		t.Error("Expected router, got nil")
	}
}

func TestNewServer_DefaultValues(t *testing.T) {
	gin.SetMode(gin.TestMode)

	mockConfig := &mockConfigProvider{
		values: map[string]any{
			"environment": "test",
		},
	}

	mockLogger := &mockLogger{}
	healthRegistry := health.NewRegistry()
	metricsRegistry := metrics.NewMetrics()
	mockErrorBus := &mockErrorBusServer{}
	tracer := noop.NewTracerProvider()

	srv, err := NewServer(mockConfig, mockLogger, healthRegistry, metricsRegistry, mockErrorBus, tracer)
	if err != nil {
		t.Fatalf("Failed to create server: %v", err)
	}

	if srv.httpServer.Addr != "0.0.0.0:8080" {
		t.Errorf("Expected default address 0.0.0.0:8080, got %s", srv.httpServer.Addr)
	}
}

func TestServer_Router(t *testing.T) {
	gin.SetMode(gin.TestMode)

	mockConfig := &mockConfigProvider{
		values: map[string]any{
			"environment": "test",
		},
	}

	mockLogger := &mockLogger{}
	healthRegistry := health.NewRegistry()
	metricsRegistry := metrics.NewMetrics()
	mockErrorBus := &mockErrorBusServer{}
	tracer := noop.NewTracerProvider()

	srv, err := NewServer(mockConfig, mockLogger, healthRegistry, metricsRegistry, mockErrorBus, tracer)
	if err != nil {
		t.Fatalf("Failed to create server: %v", err)
	}

	router := srv.Router()
	if router == nil {
		t.Error("Expected router, got nil")
	}
}

func TestServer_Shutdown(t *testing.T) {
	gin.SetMode(gin.TestMode)

	mockConfig := &mockConfigProvider{
		values: map[string]any{
			"environment": "test",
			"server.port": 0, // Use random port
		},
	}

	mockLogger := &mockLogger{}
	healthRegistry := health.NewRegistry()
	metricsRegistry := metrics.NewMetrics()
	mockErrorBus := &mockErrorBusServer{}
	tracer := noop.NewTracerProvider()

	srv, err := NewServer(mockConfig, mockLogger, healthRegistry, metricsRegistry, mockErrorBus, tracer)
	if err != nil {
		t.Fatalf("Failed to create server: %v", err)
	}

	// Start server in background
	go func() {
		_ = srv.Start()
	}()

	// Wait a bit for server to start
	time.Sleep(100 * time.Millisecond)

	// Shutdown
	ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
	defer cancel()

	if err := srv.Shutdown(ctx); err != nil {
		t.Errorf("Shutdown failed: %v", err)
	}
}

func TestServer_HealthEndpoints(t *testing.T) {
	gin.SetMode(gin.TestMode)

	mockConfig := &mockConfigProvider{
		values: map[string]any{
			"environment": "test",
		},
	}

	mockLogger := &mockLogger{}
	healthRegistry := health.NewRegistry()
	metricsRegistry := metrics.NewMetrics()
	mockErrorBus := &mockErrorBusServer{}
	tracer := noop.NewTracerProvider()

	srv, err := NewServer(mockConfig, mockLogger, healthRegistry, metricsRegistry, mockErrorBus, tracer)
	if err != nil {
		t.Fatalf("Failed to create server: %v", err)
	}

	// Test /healthz endpoint
	req := httptest.NewRequest(http.MethodGet, "/healthz", nil)
	w := httptest.NewRecorder()
	srv.router.ServeHTTP(w, req)

	if w.Code != http.StatusOK {
		t.Errorf("Expected status 200 for /healthz, got %d", w.Code)
	}

	// Test /ready endpoint
	req = httptest.NewRequest(http.MethodGet, "/ready", nil)
	w = httptest.NewRecorder()
	srv.router.ServeHTTP(w, req)

	if w.Code != http.StatusOK {
		t.Errorf("Expected status 200 for /ready, got %d", w.Code)
	}
}

func TestServer_MetricsEndpoint(t *testing.T) {
	gin.SetMode(gin.TestMode)

	mockConfig := &mockConfigProvider{
		values: map[string]any{
			"environment": "test",
		},
	}

	mockLogger := &mockLogger{}
	healthRegistry := health.NewRegistry()
	metricsRegistry := metrics.NewMetrics()
	mockErrorBus := &mockErrorBusServer{}
	tracer := noop.NewTracerProvider()

	srv, err := NewServer(mockConfig, mockLogger, healthRegistry, metricsRegistry, mockErrorBus, tracer)
	if err != nil {
		t.Fatalf("Failed to create server: %v", err)
	}

	req := httptest.NewRequest(http.MethodGet, "/metrics", nil)
	w := httptest.NewRecorder()
	srv.router.ServeHTTP(w, req)

	if w.Code != http.StatusOK {
		t.Errorf("Expected status 200 for /metrics, got %d", w.Code)
	}

	// Prometheus handler may return empty body if no metrics are recorded yet
	// This is acceptable - we just verify the endpoint works
	_ = w.Body.String()
}

// mockConfigProvider implements config.ConfigProvider for testing.
type mockConfigProvider struct {
	values map[string]any
}

func (m *mockConfigProvider) Get(key string) any {
	return m.values[key]
}

func (m *mockConfigProvider) GetString(key string) string {
	if val, ok := m.values[key].(string); ok {
		return val
	}
	if val, ok := m.values[key]; ok {
		return val.(string)
	}
	return ""
}

func (m *mockConfigProvider) GetInt(key string) int {
	if val, ok := m.values[key].(int); ok {
		return val
	}
	return 0
}

func (m *mockConfigProvider) GetBool(key string) bool {
	if val, ok := m.values[key].(bool); ok {
		return val
	}
	return false
}

func (m *mockConfigProvider) GetDuration(key string) time.Duration {
	if val, ok := m.values[key].(string); ok {
		dur, err := time.ParseDuration(val)
		if err == nil {
			return dur
		}
	}
	if val, ok := m.values[key].(time.Duration); ok {
		return val
	}
	return 0
}

func (m *mockConfigProvider) GetStringSlice(key string) []string {
	if val, ok := m.values[key].([]string); ok {
		return val
	}
	return nil
}

func (m *mockConfigProvider) IsSet(key string) bool {
	_, ok := m.values[key]
	return ok
}

func (m *mockConfigProvider) Unmarshal(v any) error {
	return nil
}

// Note: mockLogger and mockErrorBusMiddleware are defined in middleware_test.go
// We use mockErrorBusServer here to avoid conflicts
type mockErrorBusServer struct {
	errors []error
	ctxs   []context.Context
}

func (m *mockErrorBusServer) Publish(ctx context.Context, err error) {
	m.errors = append(m.errors, err)
	m.ctxs = append(m.ctxs, ctx)
}
