package middleware

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

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

func TestQueryParamValidation_ValidateQueryParams(t *testing.T) {
	gin.SetMode(gin.TestMode)
	logger, _ := zap.NewDevelopment()
	queryValidation := NewQueryParamValidation(logger)

	tests := []struct {
		name           string
		url            string
		rules          map[string]string
		expectedStatus int
		shouldAbort    bool
	}{
		{
			name: "valid numeric params",
			url:  "/test?page=1&limit=10",
			rules: map[string]string{
				"page":  "numeric,min=1",
				"limit": "numeric,min=1,max=100",
			},
			expectedStatus: http.StatusOK,
			shouldAbort:    false,
		},
		{
			name: "invalid numeric param",
			url:  "/test?page=abc",
			rules: map[string]string{
				"page": "numeric",
			},
			expectedStatus: http.StatusBadRequest,
			shouldAbort:    true,
		},
		{
			name: "param below minimum",
			url:  "/test?page=0",
			rules: map[string]string{
				"page": "numeric,min=1",
			},
			expectedStatus: http.StatusBadRequest,
			shouldAbort:    true,
		},
		{
			name: "param above maximum",
			url:  "/test?limit=200",
			rules: map[string]string{
				"limit": "numeric,max=100",
			},
			expectedStatus: http.StatusBadRequest,
			shouldAbort:    true,
		},
		{
			name: "oneof validation - valid",
			url:  "/test?sort=asc",
			rules: map[string]string{
				"sort": "oneof=asc,desc",
			},
			expectedStatus: http.StatusOK,
			shouldAbort:    false,
		},
		{
			name: "oneof validation - invalid",
			url:  "/test?sort=invalid",
			rules: map[string]string{
				"sort": "oneof=asc,desc",
			},
			expectedStatus: http.StatusBadRequest,
			shouldAbort:    true,
		},
		{
			name: "required param missing",
			url:  "/test",
			rules: map[string]string{
				"page": "required,numeric",
			},
			expectedStatus: http.StatusBadRequest,
			shouldAbort:    true,
		},
		{
			name: "optional param missing",
			url:  "/test",
			rules: map[string]string{
				"page": "numeric",
			},
			expectedStatus: http.StatusOK,
			shouldAbort:    false,
		},
		{
			name: "valid UUID",
			url:  "/test?id=550e8400-e29b-41d4-a716-446655440000",
			rules: map[string]string{
				"id": "uuid",
			},
			expectedStatus: http.StatusOK,
			shouldAbort:    false,
		},
		{
			name: "invalid UUID",
			url:  "/test?id=invalid-uuid",
			rules: map[string]string{
				"id": "uuid",
			},
			expectedStatus: http.StatusBadRequest,
			shouldAbort:    true,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			router := gin.New()
			router.GET("/test", queryValidation.ValidateQueryParams(tt.rules), func(c *gin.Context) {
				c.JSON(http.StatusOK, gin.H{"ok": true})
			})

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

			assert.Equal(t, tt.expectedStatus, w.Code)
			if tt.shouldAbort {
				assert.Contains(t, w.Body.String(), "Invalid query parameters")
			} else {
				assert.Contains(t, w.Body.String(), "ok")
			}
		})
	}
}

func TestParseRules(t *testing.T) {
	tests := []struct {
		name     string
		ruleStr  string
		expected []rule
	}{
		{
			name:    "single rule",
			ruleStr: "numeric",
			expected: []rule{
				{name: "numeric", param: ""},
			},
		},
		{
			name:    "rule with param",
			ruleStr: "min=1",
			expected: []rule{
				{name: "min", param: "1"},
			},
		},
		{
			name:    "multiple rules",
			ruleStr: "numeric,min=1,max=100",
			expected: []rule{
				{name: "numeric", param: ""},
				{name: "min", param: "1"},
				{name: "max", param: "100"},
			},
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			result := parseRules(tt.ruleStr)
			require.Equal(t, len(tt.expected), len(result))
			for i, expected := range tt.expected {
				assert.Equal(t, expected.name, result[i].name)
				assert.Equal(t, expected.param, result[i].param)
			}
		})
	}
}

func TestContainsRule(t *testing.T) {
	tests := []struct {
		name     string
		ruleStr  string
		ruleName string
		expected bool
	}{
		{"contains", "numeric,min=1", "min", true},
		{"not contains", "numeric,min=1", "max", false},
		{"single rule", "numeric", "numeric", true},
		{"empty", "", "numeric", false},
	}

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