package middleware

import (
	"bytes"
	"errors"
	"net/http"
	"net/http/httptest"
	"testing"

	"github.com/gin-gonic/gin"
	"github.com/rs/zerolog"
	"github.com/rs/zerolog/log"
	"github.com/stretchr/testify/assert"
)

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

	var buf bytes.Buffer
	originalLogger := log.Logger
	log.Logger = zerolog.New(&buf)
	defer func() { log.Logger = originalLogger }()

	r := gin.New()
	r.Use(RequestID())
	r.Use(RequestLogger())
	r.GET("/test", func(c *gin.Context) {
		c.String(http.StatusOK, "ok")
	})

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

	assert.Equal(t, http.StatusOK, w.Code)
	logOutput := buf.String()
	assert.Contains(t, logOutput, "HTTP Request")
	assert.Contains(t, logOutput, "GET")
	assert.Contains(t, logOutput, "/test")
}

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

	var buf bytes.Buffer
	originalLogger := log.Logger
	log.Logger = zerolog.New(&buf)
	defer func() { log.Logger = originalLogger }()

	r := gin.New()
	r.Use(RequestID())
	r.Use(RequestLogger())
	r.GET("/search", func(c *gin.Context) {
		c.String(http.StatusOK, "ok")
	})

	req := httptest.NewRequest("GET", "/search?q=test&limit=10", nil)
	w := httptest.NewRecorder()
	r.ServeHTTP(w, req)

	assert.Equal(t, http.StatusOK, w.Code)
	logOutput := buf.String()
	assert.Contains(t, logOutput, "/search?q=test&limit=10")
}

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

	var buf bytes.Buffer
	originalLogger := log.Logger
	log.Logger = zerolog.New(&buf)
	defer func() { log.Logger = originalLogger }()

	r := gin.New()
	r.Use(RequestID())
	r.Use(RequestLogger())
	r.GET("/error", func(c *gin.Context) {
		c.String(http.StatusInternalServerError, "error")
	})

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

	assert.Equal(t, http.StatusInternalServerError, w.Code)
	logOutput := buf.String()
	assert.Contains(t, logOutput, "error")
}

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

	var buf bytes.Buffer
	originalLogger := log.Logger
	log.Logger = zerolog.New(&buf)
	defer func() { log.Logger = originalLogger }()

	r := gin.New()
	r.Use(RequestID())
	r.Use(RequestLogger())
	r.GET("/test", func(c *gin.Context) {
		c.String(http.StatusOK, "ok")
	})

	clientID := "test-request-id-123"
	req := httptest.NewRequest("GET", "/test", nil)
	req.Header.Set("X-Request-ID", clientID)
	w := httptest.NewRecorder()
	r.ServeHTTP(w, req)

	logOutput := buf.String()
	assert.Contains(t, logOutput, clientID)
}

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

	var buf bytes.Buffer
	originalLogger := log.Logger
	log.Logger = zerolog.New(&buf)
	defer func() { log.Logger = originalLogger }()

	r := gin.New()
	r.Use(RequestID())
	r.Use(RequestLogger())
	r.GET("/test", func(c *gin.Context) {
		c.String(http.StatusOK, "ok")
	})

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

	logOutput := buf.String()
	assert.Contains(t, logOutput, "latency")
}

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

	var buf bytes.Buffer
	originalLogger := log.Logger
	log.Logger = zerolog.New(&buf)
	defer func() { log.Logger = originalLogger }()

	r := gin.New()
	r.Use(RequestID())
	r.Use(RequestLogger())
	r.GET("/notfound", func(c *gin.Context) {
		c.String(http.StatusNotFound, "not found")
	})

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

	assert.Equal(t, http.StatusNotFound, w.Code)
	logOutput := buf.String()
	assert.Contains(t, logOutput, "HTTP Request")
	assert.Contains(t, logOutput, "warn")
}

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

	var buf bytes.Buffer
	originalLogger := log.Logger
	log.Logger = zerolog.New(&buf)
	defer func() { log.Logger = originalLogger }()

	r := gin.New()
	r.Use(RequestID())
	r.Use(RequestLogger())
	r.GET("/err", func(c *gin.Context) {
		_ = c.Error(errors.New("something went wrong"))
		c.String(http.StatusOK, "ok")
	})

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

	logOutput := buf.String()
	assert.Contains(t, logOutput, "Request Error")
	assert.Contains(t, logOutput, "something went wrong")
}

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

	var buf bytes.Buffer
	originalLogger := log.Logger
	log.Logger = zerolog.New(&buf)
	defer func() { log.Logger = originalLogger }()

	r := gin.New()
	r.Use(RequestID())
	r.Use(Tracing())
	r.Use(RequestLogger())
	r.GET("/test", func(c *gin.Context) {
		c.String(http.StatusOK, "ok")
	})

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

	logOutput := buf.String()
	assert.Contains(t, logOutput, "trace_id")
}

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

	var buf bytes.Buffer
	originalLogger := log.Logger
	log.Logger = zerolog.New(&buf)
	defer func() { log.Logger = originalLogger }()

	r := gin.New()
	r.Use(RequestLogger())
	r.GET("/test", func(c *gin.Context) {
		c.String(http.StatusOK, "ok")
	})

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

	logOutput := buf.String()
	assert.Contains(t, logOutput, `"request_id":"-"`)
}
