package middleware

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

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

func TestRequestID_GeneratesNewIDWhenMissing(t *testing.T) {
	gin.SetMode(gin.TestMode)
	r := gin.New()
	r.Use(RequestID())

	var capturedID string
	r.GET("/test", func(c *gin.Context) {
		capturedID = GetRequestID(c)
		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)
	assert.NotEmpty(t, capturedID, "Request ID should be generated")
	assert.Equal(t, capturedID, w.Header().Get(HeaderXRequestID), "Request ID should be in response header")
}

func TestRequestID_UsesClientProvidedID(t *testing.T) {
	gin.SetMode(gin.TestMode)
	r := gin.New()
	r.Use(RequestID())

	clientID := "client-generated-uuid-12345"
	var capturedID string
	r.GET("/test", func(c *gin.Context) {
		capturedID = GetRequestID(c)
		c.String(http.StatusOK, "ok")
	})

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

	assert.Equal(t, http.StatusOK, w.Code)
	assert.Equal(t, clientID, capturedID, "Should use client-provided Request ID")
	assert.Equal(t, clientID, w.Header().Get(HeaderXRequestID), "Response should echo client ID")
}

func TestRequestID_IsAccessibleFromContext(t *testing.T) {
	gin.SetMode(gin.TestMode)
	r := gin.New()
	r.Use(RequestID())

	var fromContext, fromHelper string
	r.GET("/test", func(c *gin.Context) {
		raw, exists := c.Get(ContextKeyRequestID)
		if exists {
			fromContext = raw.(string)
		}
		fromHelper = GetRequestID(c)
		c.String(http.StatusOK, "ok")
	})

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

	assert.NotEmpty(t, fromContext)
	assert.Equal(t, fromContext, fromHelper, "Both methods should return same ID")
}

// TestRequestID_UsesReplayHeader covers the `cmd/replay --target-dsn`
// correlation path (§14.5): the replay CLI sends `X-Replay-RequestId`
// with the captured request's original RequestId, and the target backend
// must stamp AuditLog.RequestId with that value so the differ can find
// the rows it just produced.
func TestRequestID_UsesReplayHeader(t *testing.T) {
	gin.SetMode(gin.TestMode)
	r := gin.New()
	r.Use(RequestID())

	var capturedID string
	r.GET("/test", func(c *gin.Context) {
		capturedID = GetRequestID(c)
		c.String(http.StatusOK, "ok")
	})

	req := httptest.NewRequest("GET", "/test", nil)
	req.Header.Set(HeaderXReplayRequestID, "replay-src-abc")
	w := httptest.NewRecorder()
	r.ServeHTTP(w, req)

	assert.Equal(t, "replay-src-abc", capturedID, "Should use X-Replay-RequestId")
	assert.Equal(t, "replay-src-abc", w.Header().Get(HeaderXRequestID))
}

// TestRequestID_ReplayHeaderPrecedence: when both headers present, the
// replay header wins (more specific intent — a caller deliberately asking
// for correlation in a replay context).
func TestRequestID_ReplayHeaderPrecedence(t *testing.T) {
	gin.SetMode(gin.TestMode)
	r := gin.New()
	r.Use(RequestID())

	var capturedID string
	r.GET("/test", func(c *gin.Context) {
		capturedID = GetRequestID(c)
		c.String(http.StatusOK, "ok")
	})

	req := httptest.NewRequest("GET", "/test", nil)
	req.Header.Set(HeaderXRequestID, "generic-id")
	req.Header.Set(HeaderXReplayRequestID, "replay-id")
	w := httptest.NewRecorder()
	r.ServeHTTP(w, req)

	assert.Equal(t, "replay-id", capturedID, "Replay header must take precedence")
}

func TestGetRequestID_ReturnsEmptyForMissingContext(t *testing.T) {
	gin.SetMode(gin.TestMode)
	r := gin.New()

	var result string
	r.GET("/test", func(c *gin.Context) {
		result = GetRequestID(c)
		c.String(http.StatusOK, "ok")
	})

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

	assert.Empty(t, result, "Should return empty string when request ID not set")
}

