package middleware

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

	"github.com/gin-gonic/gin"
	"github.com/stretchr/testify/assert"
	"go.uber.org/zap"
	"gorm.io/gorm"
	"veza-backend-api/internal/errors"
	"veza-backend-api/internal/metrics"
)

func TestErrorHandler_AppError(t *testing.T) {
	gin.SetMode(gin.TestMode)
	logger := zap.NewNop()
	errorMetrics := metrics.NewErrorMetrics()
	router := gin.New()
	router.Use(ErrorHandler(logger, errorMetrics))
	router.GET("/test", func(c *gin.Context) {
		c.Error(errors.NewNotFoundError("User"))
	})

	w := httptest.NewRecorder()
	req := httptest.NewRequest("GET", "/test", nil)
	router.ServeHTTP(w, req)

	assert.Equal(t, http.StatusNotFound, w.Code)

	var response map[string]interface{}
	err := json.Unmarshal(w.Body.Bytes(), &response)
	assert.NoError(t, err)

	errorObj := response["error"].(map[string]interface{})
	assert.Equal(t, float64(errors.ErrCodeNotFound), errorObj["code"])
	assert.Contains(t, errorObj["message"].(string), "not found")
}

func TestErrorHandler_GORMError(t *testing.T) {
	gin.SetMode(gin.TestMode)
	logger := zap.NewNop()
	router := gin.New()
	errorMetrics := metrics.NewErrorMetrics()
	router.Use(ErrorHandler(logger, errorMetrics))
	router.GET("/test", func(c *gin.Context) {
		c.Error(gorm.ErrRecordNotFound)
	})

	w := httptest.NewRecorder()
	req := httptest.NewRequest("GET", "/test", nil)
	router.ServeHTTP(w, req)

	assert.Equal(t, http.StatusNotFound, w.Code)

	var response map[string]interface{}
	err := json.Unmarshal(w.Body.Bytes(), &response)
	assert.NoError(t, err)

	errorObj := response["error"].(map[string]interface{})
	assert.Equal(t, float64(errors.ErrCodeNotFound), errorObj["code"])
	assert.Equal(t, "Resource not found", errorObj["message"])
}

func TestErrorHandler_GenericError(t *testing.T) {
	gin.SetMode(gin.TestMode)
	logger := zap.NewNop()
	router := gin.New()
	errorMetrics := metrics.NewErrorMetrics()
	router.Use(ErrorHandler(logger, errorMetrics))
	router.GET("/test", func(c *gin.Context) {
		c.Error(assert.AnError)
	})

	w := httptest.NewRecorder()
	req := httptest.NewRequest("GET", "/test", nil)
	router.ServeHTTP(w, req)

	assert.Equal(t, http.StatusInternalServerError, w.Code)

	var response map[string]interface{}
	err := json.Unmarshal(w.Body.Bytes(), &response)
	assert.NoError(t, err)

	errorObj := response["error"].(map[string]interface{})
	assert.Equal(t, float64(errors.ErrCodeInternal), errorObj["code"])
	assert.Equal(t, "Internal server error", errorObj["message"])
}

func TestErrorHandler_ValidationError(t *testing.T) {
	gin.SetMode(gin.TestMode)
	logger := zap.NewNop()
	router := gin.New()
	errorMetrics := metrics.NewErrorMetrics()
	router.Use(ErrorHandler(logger, errorMetrics))
	router.GET("/test", func(c *gin.Context) {
		validationErr := errors.NewValidationError("Validation failed",
			errors.ErrorDetail{Field: "email", Message: "Invalid email format"},
		)
		c.Error(validationErr)
	})

	w := httptest.NewRecorder()
	req := httptest.NewRequest("GET", "/test", nil)
	router.ServeHTTP(w, req)

	assert.Equal(t, http.StatusBadRequest, w.Code)

	var response map[string]interface{}
	err := json.Unmarshal(w.Body.Bytes(), &response)
	assert.NoError(t, err)

	errorObj := response["error"].(map[string]interface{})
	assert.Equal(t, float64(errors.ErrCodeValidation), errorObj["code"])
	assert.Equal(t, "Validation failed", errorObj["message"])
	assert.NotNil(t, errorObj["details"])
}

func TestErrorHandler_UnauthorizedError(t *testing.T) {
	gin.SetMode(gin.TestMode)
	logger := zap.NewNop()
	router := gin.New()
	errorMetrics := metrics.NewErrorMetrics()
	router.Use(ErrorHandler(logger, errorMetrics))
	router.GET("/test", func(c *gin.Context) {
		c.Error(errors.NewUnauthorizedError("Invalid credentials"))
	})

	w := httptest.NewRecorder()
	req := httptest.NewRequest("GET", "/test", nil)
	router.ServeHTTP(w, req)

	assert.Equal(t, http.StatusUnauthorized, w.Code)

	var response map[string]interface{}
	err := json.Unmarshal(w.Body.Bytes(), &response)
	assert.NoError(t, err)

	errorObj := response["error"].(map[string]interface{})
	assert.Equal(t, float64(errors.ErrCodeUnauthorized), errorObj["code"])
}

func TestMapErrorCodeToHTTPStatus(t *testing.T) {
	tests := []struct {
		name     string
		code     errors.ErrorCode
		expected int
	}{
		{"Unauthorized", errors.ErrCodeUnauthorized, http.StatusUnauthorized},
		{"Forbidden", errors.ErrCodeForbidden, http.StatusForbidden},
		{"Validation", errors.ErrCodeValidation, http.StatusBadRequest},
		{"NotFound", errors.ErrCodeNotFound, http.StatusNotFound},
		{"AlreadyExists", errors.ErrCodeAlreadyExists, http.StatusConflict},
		{"RateLimit", errors.ErrCodeRateLimitExceeded, http.StatusTooManyRequests},
		{"Internal", errors.ErrCodeInternal, http.StatusInternalServerError},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			result := mapErrorCodeToHTTPStatus(tt.code)
			assert.Equal(t, tt.expected, result)
		})
	}
}

func TestErrorHandler_NoErrors(t *testing.T) {
	gin.SetMode(gin.TestMode)
	logger := zap.NewNop()
	router := gin.New()
	errorMetrics := metrics.NewErrorMetrics()
	router.Use(ErrorHandler(logger, errorMetrics))
	router.GET("/test", func(c *gin.Context) {
		c.JSON(http.StatusOK, gin.H{"success": true})
	})

	w := httptest.NewRecorder()
	req := httptest.NewRequest("GET", "/test", nil)
	router.ServeHTTP(w, req)

	assert.Equal(t, http.StatusOK, w.Code)
	assert.Contains(t, w.Body.String(), "success")
}

func TestErrorHandler_MultipleErrors(t *testing.T) {
	gin.SetMode(gin.TestMode)
	logger := zap.NewNop()
	router := gin.New()
	errorMetrics := metrics.NewErrorMetrics()
	router.Use(ErrorHandler(logger, errorMetrics))
	router.GET("/test", func(c *gin.Context) {
		c.Error(errors.NewValidationError("First error"))
		c.Error(errors.NewNotFoundError("Second error"))
		// Seule la dernière erreur doit être traitée
	})

	w := httptest.NewRecorder()
	req := httptest.NewRequest("GET", "/test", nil)
	router.ServeHTTP(w, req)

	assert.Equal(t, http.StatusNotFound, w.Code)

	var response map[string]interface{}
	err := json.Unmarshal(w.Body.Bytes(), &response)
	assert.NoError(t, err)

	errorObj := response["error"].(map[string]interface{})
	assert.Equal(t, float64(errors.ErrCodeNotFound), errorObj["code"])
}

func TestErrorHandler_ContextPropagation_RequestID(t *testing.T) {
	gin.SetMode(gin.TestMode)
	logger := zap.NewNop()
	router := gin.New()
	router.Use(RequestID()) // On est déjà dans le package middleware
	errorMetrics := metrics.NewErrorMetrics()
	router.Use(ErrorHandler(logger, errorMetrics))
	router.GET("/test", func(c *gin.Context) {
		c.Error(errors.NewNotFoundError("User"))
	})

	w := httptest.NewRecorder()
	req := httptest.NewRequest("GET", "/test", nil)
	router.ServeHTTP(w, req)

	assert.Equal(t, http.StatusNotFound, w.Code)

	var response map[string]interface{}
	err := json.Unmarshal(w.Body.Bytes(), &response)
	assert.NoError(t, err)

	errorObj := response["error"].(map[string]interface{})
	assert.NotNil(t, errorObj["context"])

	context := errorObj["context"].(map[string]interface{})
	assert.NotEmpty(t, context["request_id"])

	// Vérifier que le request_id dans la réponse correspond au header
	assert.Equal(t, w.Header().Get("X-Request-ID"), context["request_id"])
}

func TestErrorHandler_ContextPropagation_UserID(t *testing.T) {
	gin.SetMode(gin.TestMode)
	logger := zap.NewNop()
	router := gin.New()
	router.Use(RequestID()) // On est déjà dans le package middleware
	errorMetrics := metrics.NewErrorMetrics()
	router.Use(ErrorHandler(logger, errorMetrics))
	router.GET("/test", func(c *gin.Context) {
		// Simuler un user_id dans le contexte
		c.Set("user_id", int64(42))
		c.Error(errors.NewValidationError("Validation failed"))
	})

	w := httptest.NewRecorder()
	req := httptest.NewRequest("GET", "/test", nil)
	router.ServeHTTP(w, req)

	assert.Equal(t, http.StatusBadRequest, w.Code)

	var response map[string]interface{}
	err := json.Unmarshal(w.Body.Bytes(), &response)
	assert.NoError(t, err)

	errorObj := response["error"].(map[string]interface{})
	assert.NotNil(t, errorObj["context"])

	context := errorObj["context"].(map[string]interface{})
	assert.NotEmpty(t, context["request_id"])
	assert.Equal(t, float64(42), context["user_id"])
}

func TestErrorHandler_ContextPropagation_BothIDs(t *testing.T) {
	gin.SetMode(gin.TestMode)
	logger := zap.NewNop()
	router := gin.New()
	router.Use(RequestID()) // On est déjà dans le package middleware
	errorMetrics := metrics.NewErrorMetrics()
	router.Use(ErrorHandler(logger, errorMetrics))
	router.GET("/test", func(c *gin.Context) {
		c.Set("user_id", "user-123")
		c.Error(errors.NewNotFoundError("Resource"))
	})

	w := httptest.NewRecorder()
	req := httptest.NewRequest("GET", "/test", nil)
	router.ServeHTTP(w, req)

	assert.Equal(t, http.StatusNotFound, w.Code)

	var response map[string]interface{}
	err := json.Unmarshal(w.Body.Bytes(), &response)
	assert.NoError(t, err)

	errorObj := response["error"].(map[string]interface{})
	context := errorObj["context"].(map[string]interface{})

	assert.NotEmpty(t, context["request_id"])
	assert.Equal(t, "user-123", context["user_id"])
}

func TestEnrichErrorWithContext_NoContext(t *testing.T) {
	gin.SetMode(gin.TestMode)
	c, _ := gin.CreateTestContext(httptest.NewRecorder())
	c.Request = httptest.NewRequest("GET", "/test", nil)

	appErr := errors.New(errors.ErrCodeValidation, "Test error")
	enrichErrorWithContext(c, appErr)

	assert.NotNil(t, appErr.Context)
	// Sans RequestID middleware, request_id ne sera pas présent
	assert.NotContains(t, appErr.Context, "request_id")
}

func TestEnrichErrorWithContext_ExistingContext(t *testing.T) {
	gin.SetMode(gin.TestMode)
	c, _ := gin.CreateTestContext(httptest.NewRecorder())
	c.Request = httptest.NewRequest("GET", "/test", nil)
	c.Set("request_id", "existing-request-id")
	c.Set("user_id", int64(99))

	appErr := errors.New(errors.ErrCodeValidation, "Test error")
	appErr.Context = map[string]interface{}{
		"existing_field": "value",
	}

	enrichErrorWithContext(c, appErr)

	assert.Equal(t, "existing-request-id", appErr.Context["request_id"])
	assert.Equal(t, int64(99), appErr.Context["user_id"])
	assert.Equal(t, "value", appErr.Context["existing_field"])
}
