package handlers

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

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

	"veza-backend-api/internal/dto"
)

func TestBindAndValidateJSON_Validation(t *testing.T) {
	gin.SetMode(gin.TestMode)
	logger := zap.NewNop()
	commonHandler := NewCommonHandler(logger)

	// Test case struct
	type TestRequest struct {
		Name  string `json:"name" validate:"required,min=3"`
		Email string `json:"email" validate:"required,email"`
		Age   int    `json:"age" validate:"gte=18"`
	}

	tests := []struct {
		name           string
		payload        interface{}
		expectedStatus int
		expectedError  string
		checkDetails   bool
	}{
		{
			name: "Valid Request",
			payload: TestRequest{
				Name:  "John Doe",
				Email: "john@example.com",
				Age:   25,
			},
			expectedStatus: http.StatusOK,
		},
		{
			name: "Missing Required Fields",
			payload: map[string]interface{}{
				"age": 25,
			},
			expectedStatus: http.StatusBadRequest, // or 422 depending on implementation, BindAndValidateJSON returns 400 for validation error wrapper
			expectedError:  "Validation failed",
			checkDetails:   true,
		},
		{
			name: "Invalid Email",
			payload: TestRequest{
				Name:  "John",
				Email: "not-an-email",
				Age:   25,
			},
			expectedStatus: http.StatusBadRequest,
			expectedError:  "Validation failed",
			checkDetails:   true,
		},
		{
			name: "Min Length Violation",
			payload: TestRequest{
				Name:  "Jo",
				Email: "john@example.com",
				Age:   25,
			},
			expectedStatus: http.StatusBadRequest,
			expectedError:  "Validation failed",
			checkDetails:   true,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			w := httptest.NewRecorder()
			c, _ := gin.CreateTestContext(w)

			jsonBytes, _ := json.Marshal(tt.payload)
			c.Request = httptest.NewRequest("POST", "/test", bytes.NewBuffer(jsonBytes))
			c.Request.Header.Set("Content-Type", "application/json")

			var req TestRequest
			appErr := commonHandler.BindAndValidateJSON(c, &req)

			if tt.expectedStatus == http.StatusOK {
				assert.Nil(t, appErr)
			} else {
				assert.NotNil(t, appErr)
				// Verify error response format by actually calling RespondWithAppError logic simulation
				// Or check AppError properties
				assert.Contains(t, appErr.Message, tt.expectedError)

				if tt.checkDetails {
					// check logic in commonHandler.BindAndValidateJSON
					// It returns apperrors.NewValidationError which puts details in Details field
					assert.NotEmpty(t, appErr.Details, "Should have validation details")
				}
			}
		})
	}
}

func TestBindAndValidateJSON_DTOs(t *testing.T) {
	// Verify actual DTOs used in handlers
	gin.SetMode(gin.TestMode)
	logger := zap.NewNop()
	commonHandler := NewCommonHandler(logger)

	t.Run("RegisterRequest_Invalid", func(t *testing.T) {
		payload := dto.RegisterRequest{
			Username: "ab", // Too short
			Email:    "bad-email",
			Password: "short",
		}

		w := httptest.NewRecorder()
		c, _ := gin.CreateTestContext(w)
		jsonBytes, _ := json.Marshal(commonHandler) // Mistake in previous line logic, fix payload
		jsonBytes, _ = json.Marshal(payload)

		c.Request = httptest.NewRequest("POST", "/register", bytes.NewBuffer(jsonBytes))
		var req dto.RegisterRequest
		appErr := commonHandler.BindAndValidateJSON(c, &req)

		assert.NotNil(t, appErr)
		// Should catch Username min, Email format, Password min
		assert.NotEmpty(t, appErr.Details)
	})

	t.Run("LoginRequest_Invalid", func(t *testing.T) {
		payload := dto.LoginRequest{
			Email: "bad-email",
		}

		w := httptest.NewRecorder()
		c, _ := gin.CreateTestContext(w)
		jsonBytes, _ := json.Marshal(payload)

		c.Request = httptest.NewRequest("POST", "/login", bytes.NewBuffer(jsonBytes))
		var req dto.LoginRequest
		appErr := commonHandler.BindAndValidateJSON(c, &req)

		assert.NotNil(t, appErr)
		assert.NotEmpty(t, appErr.Details)
	})
}
