package server

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

	"git.dcentral.systems/toolz/goplt/pkg/logger"
	"github.com/gin-gonic/gin"
)

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

	router := gin.New()
	router.Use(RequestIDMiddleware())

	router.GET("/test", func(c *gin.Context) {
		requestID, exists := c.Get(string(requestIDKey))
		if !exists {
			t.Error("Expected request ID in context")
		}
		if requestID == nil || requestID == "" {
			t.Error("Expected non-empty request ID")
		}
		c.JSON(http.StatusOK, gin.H{"request_id": requestID})
	})

	req := httptest.NewRequest(http.MethodGet, "/test", nil)
	w := httptest.NewRecorder()

	router.ServeHTTP(w, req)

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

	// Verify X-Request-ID header is set
	if w.Header().Get("X-Request-ID") == "" {
		t.Error("Expected X-Request-ID header")
	}
}

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

	router := gin.New()
	router.Use(RequestIDMiddleware())

	router.GET("/test", func(c *gin.Context) {
		c.JSON(http.StatusOK, gin.H{"ok": true})
	})

	req := httptest.NewRequest(http.MethodGet, "/test", nil)
	req.Header.Set("X-Request-ID", "existing-id")
	w := httptest.NewRecorder()

	router.ServeHTTP(w, req)

	if w.Header().Get("X-Request-ID") != "existing-id" {
		t.Errorf("Expected existing request ID, got %s", w.Header().Get("X-Request-ID"))
	}
}

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

	mockLogger := &mockLogger{}
	router := gin.New()
	router.Use(RequestIDMiddleware())
	router.Use(LoggingMiddleware(mockLogger))

	router.GET("/test", func(c *gin.Context) {
		c.JSON(http.StatusOK, gin.H{"message": "test"})
	})

	req := httptest.NewRequest(http.MethodGet, "/test", nil)
	w := httptest.NewRecorder()

	router.ServeHTTP(w, req)

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

	// Verify logging was called
	if len(mockLogger.infoLogs) == 0 {
		t.Error("Expected info log to be called")
	}
}

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

	mockErrorBus := &mockErrorBusMiddleware{}

	router := gin.New()
	router.Use(PanicRecoveryMiddleware(mockErrorBus))

	router.GET("/panic", func(c *gin.Context) {
		panic("test panic")
	})

	req := httptest.NewRequest(http.MethodGet, "/panic", nil)
	w := httptest.NewRecorder()

	router.ServeHTTP(w, req)

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

	// Verify error was published to error bus
	if len(mockErrorBus.errors) == 0 {
		t.Error("Expected error to be published to error bus")
	}
}

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

	mockErrorBus := &mockErrorBusMiddleware{}

	router := gin.New()
	router.Use(PanicRecoveryMiddleware(mockErrorBus))

	router.GET("/panic-error", func(c *gin.Context) {
		panic(errors.New("test error"))
	})

	req := httptest.NewRequest(http.MethodGet, "/panic-error", nil)
	w := httptest.NewRecorder()

	router.ServeHTTP(w, req)

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

	if len(mockErrorBus.errors) == 0 {
		t.Error("Expected error to be published to error bus")
	}
}

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

	router := gin.New()
	router.Use(CORSMiddleware())

	router.GET("/test", func(c *gin.Context) {
		c.JSON(http.StatusOK, gin.H{"message": "test"})
	})

	req := httptest.NewRequest(http.MethodGet, "/test", nil)
	w := httptest.NewRecorder()

	router.ServeHTTP(w, req)

	// Verify CORS headers
	if w.Header().Get("Access-Control-Allow-Origin") != "*" {
		t.Error("Expected CORS header Access-Control-Allow-Origin")
	}

	if w.Header().Get("Access-Control-Allow-Credentials") != "true" {
		t.Error("Expected CORS header Access-Control-Allow-Credentials")
	}
}

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

	router := gin.New()
	router.Use(CORSMiddleware())

	router.GET("/test", func(c *gin.Context) {
		c.JSON(http.StatusOK, gin.H{"message": "test"})
	})

	req := httptest.NewRequest(http.MethodOptions, "/test", nil)
	w := httptest.NewRecorder()

	router.ServeHTTP(w, req)

	if w.Code != http.StatusNoContent {
		t.Errorf("Expected status 204 for OPTIONS, got %d", w.Code)
	}
}

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

	router := gin.New()
	router.Use(TimeoutMiddleware(100 * time.Millisecond))

	router.GET("/test", func(c *gin.Context) {
		c.JSON(http.StatusOK, gin.H{"message": "test"})
	})

	req := httptest.NewRequest(http.MethodGet, "/test", nil)
	w := httptest.NewRecorder()

	router.ServeHTTP(w, req)

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

// mockLogger implements logger.Logger for testing.
type mockLogger struct {
	infoLogs []string
	errors   []string
}

func (m *mockLogger) Debug(msg string, fields ...logger.Field) {}
func (m *mockLogger) Info(msg string, fields ...logger.Field) {
	m.infoLogs = append(m.infoLogs, msg)
}
func (m *mockLogger) Warn(msg string, fields ...logger.Field) {}
func (m *mockLogger) Error(msg string, fields ...logger.Field) {
	m.errors = append(m.errors, msg)
}
func (m *mockLogger) With(fields ...logger.Field) logger.Logger {
	return m
}
func (m *mockLogger) WithContext(ctx context.Context) logger.Logger {
	return m
}

// mockErrorBusMiddleware implements errorbus.ErrorPublisher for testing middleware.
type mockErrorBusMiddleware struct {
	errors []error
	ctxs   []context.Context
}

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