package middleware

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

	"veza-backend-api/internal/errors"
	"veza-backend-api/internal/metrics"

	"github.com/gin-gonic/gin"
	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"
	"go.uber.org/zap"
)

func TestErrorHandler_RecordsMetrics(t *testing.T) {
	gin.SetMode(gin.TestMode)
	logger := zap.NewNop()
	errorMetrics := metrics.NewErrorMetrics()
	router := gin.New()
	router.Use(ErrorHandler(logger, errorMetrics, true))
	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)

	// Vérifier que les métriques ont été enregistrées
	stats := errorMetrics.GetStats()
	assert.Equal(t, int64(1), stats["total_errors"])

	errorsByCode := stats["errors_by_code"].(map[errors.ErrorCode]int64)
	assert.Equal(t, int64(1), errorsByCode[errors.ErrCodeNotFound])

	errorsByHTTPStatus := stats["errors_by_http_status"].(map[int]int64)
	assert.Equal(t, int64(1), errorsByHTTPStatus[404])
}

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

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

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

	stats := errorMetrics.GetStats()
	assert.Equal(t, int64(1), stats["total_errors"])

	errorsByHTTPStatus := stats["errors_by_http_status"].(map[int]int64)
	assert.Equal(t, int64(1), errorsByHTTPStatus[400])
}

func TestErrorHandler_RecordsMetricsForInternalError(t *testing.T) {
	gin.SetMode(gin.TestMode)
	logger := zap.NewNop()
	errorMetrics := metrics.NewErrorMetrics()
	router := gin.New()
	router.Use(ErrorHandler(logger, errorMetrics, true))
	router.GET("/test", func(c *gin.Context) {
		c.Error(errors.New(errors.ErrCodeInternal, "Something went wrong"))
	})

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

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

	stats := errorMetrics.GetStats()
	assert.Equal(t, int64(1), stats["total_errors"])

	errorsByCode := stats["errors_by_code"].(map[errors.ErrorCode]int64)
	assert.Equal(t, int64(1), errorsByCode[errors.ErrCodeInternal])

	errorsByHTTPStatus := stats["errors_by_http_status"].(map[int]int64)
	assert.Equal(t, int64(1), errorsByHTTPStatus[500])
}

func TestErrorHandler_RecordsMultipleErrors(t *testing.T) {
	gin.SetMode(gin.TestMode)
	logger := zap.NewNop()
	errorMetrics := metrics.NewErrorMetrics()
	router := gin.New()
	router.Use(ErrorHandler(logger, errorMetrics, true))

	router.GET("/notfound", func(c *gin.Context) {
		c.Error(errors.NewNotFoundError("Resource"))
	})
	router.GET("/validation", func(c *gin.Context) {
		c.Error(errors.NewValidationError("Invalid"))
	})
	router.GET("/internal", func(c *gin.Context) {
		c.Error(errors.New(errors.ErrCodeInternal, "Error"))
	})

	// Simuler plusieurs erreurs
	httptest.NewRecorder()
	req1 := httptest.NewRequest("GET", "/notfound", nil)
	w1 := httptest.NewRecorder()
	router.ServeHTTP(w1, req1)

	req2 := httptest.NewRequest("GET", "/validation", nil)
	w2 := httptest.NewRecorder()
	router.ServeHTTP(w2, req2)

	req3 := httptest.NewRequest("GET", "/internal", nil)
	w3 := httptest.NewRecorder()
	router.ServeHTTP(w3, req3)

	stats := errorMetrics.GetStats()
	assert.Equal(t, int64(3), stats["total_errors"])

	errorsByHTTPStatus := stats["errors_by_http_status"].(map[int]int64)
	assert.Equal(t, int64(1), errorsByHTTPStatus[404])
	assert.Equal(t, int64(1), errorsByHTTPStatus[400])
	assert.Equal(t, int64(1), errorsByHTTPStatus[500])
}

func TestErrorHandler_WorksWithoutMetrics(t *testing.T) {
	gin.SetMode(gin.TestMode)
	logger := zap.NewNop()
	router := gin.New()
	// Passer nil pour les métriques - ne doit pas planter
	router.Use(ErrorHandler(logger, nil, true))
	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)
	require.NoError(t, err)

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