package middleware

import (
	"net/http"
	"reflect"
	"strings"

	"github.com/gin-gonic/gin"
	"github.com/go-playground/validator/v10"
	"go.uber.org/zap"
)

// ValidationError represents a validation error
type ValidationError struct {
	Field   string `json:"field"`
	Tag     string `json:"tag"`
	Value   string `json:"value"`
	Message string `json:"message"`
}

// ValidationResponse represents the validation error response
type ValidationResponse struct {
	Error   string            `json:"error"`
	Message string            `json:"message"`
	Details []ValidationError `json:"details,omitempty"`
}

var validate *validator.Validate

func init() {
	validate = validator.New()
	
	// Register custom tag name function to use json tags
	validate.RegisterTagNameFunc(func(fld reflect.StructField) string {
		name := strings.SplitN(fld.Tag.Get("json"), ",", 2)[0]
		if name == "-" {
			return ""
		}
		return name
	})
}

// ValidateJSON validates JSON request body against struct validation tags
func ValidateJSON(logger *zap.Logger) gin.HandlerFunc {
	return func(c *gin.Context) {
		// Skip validation for GET requests and requests without body
		if c.Request.Method == "GET" || c.Request.ContentLength == 0 {
			c.Next()
			return
		}

		// Store original body for potential re-reading
		c.Set("validation_enabled", true)
		c.Next()
	}
}

// ValidateStruct validates a struct and returns formatted errors
func ValidateStruct(s interface{}) []ValidationError {
	var errors []ValidationError
	
	err := validate.Struct(s)
	if err != nil {
		for _, err := range err.(validator.ValidationErrors) {
			var element ValidationError
			element.Field = err.Field()
			element.Tag = err.Tag()
			element.Value = err.Param()
			element.Message = getErrorMessage(err)
			errors = append(errors, element)
		}
	}
	
	return errors
}

// ValidateAndBind validates and binds JSON request to struct
func ValidateAndBind(c *gin.Context, obj interface{}) error {
	// Bind JSON to struct
	if err := c.ShouldBindJSON(obj); err != nil {
		c.JSON(http.StatusBadRequest, ValidationResponse{
			Error:   "Invalid JSON",
			Message: "Request body contains invalid JSON: " + err.Error(),
		})
		return err
	}

	// Validate struct
	if validationErrors := ValidateStruct(obj); len(validationErrors) > 0 {
		c.JSON(http.StatusBadRequest, ValidationResponse{
			Error:   "Validation Failed",
			Message: "Request validation failed",
			Details: validationErrors,
		})
		return validator.ValidationErrors{}
	}

	return nil
}

// getErrorMessage returns a human-readable error message for validation errors
func getErrorMessage(fe validator.FieldError) string {
	switch fe.Tag() {
	case "required":
		return "This field is required"
	case "email":
		return "Invalid email format"
	case "min":
		return "Value is too short (minimum " + fe.Param() + " characters)"
	case "max":
		return "Value is too long (maximum " + fe.Param() + " characters)"
	case "url":
		return "Invalid URL format"
	case "oneof":
		return "Value must be one of: " + fe.Param()
	case "uuid":
		return "Invalid UUID format"
	case "gte":
		return "Value must be greater than or equal to " + fe.Param()
	case "lte":
		return "Value must be less than or equal to " + fe.Param()
	case "len":
		return "Value must be exactly " + fe.Param() + " characters"
	case "dive":
		return "Invalid array element"
	default:
		return "Invalid value for " + fe.Field()
	}
}

// RequiredFields validates that specific fields are present in the request
func RequiredFields(fields ...string) gin.HandlerFunc {
	return func(c *gin.Context) {
		var json map[string]interface{}
		
		if err := c.ShouldBindJSON(&json); err != nil {
			c.JSON(http.StatusBadRequest, ValidationResponse{
				Error:   "Invalid JSON",
				Message: "Request body contains invalid JSON",
			})
			c.Abort()
			return
		}

		var missingFields []string
		for _, field := range fields {
			if _, exists := json[field]; !exists {
				missingFields = append(missingFields, field)
			}
		}

		if len(missingFields) > 0 {
			c.JSON(http.StatusBadRequest, ValidationResponse{
				Error:   "Missing Required Fields",
				Message: "The following required fields are missing: " + strings.Join(missingFields, ", "),
			})
			c.Abort()
			return
		}

		// Store the parsed JSON for use in handlers
		c.Set("parsed_json", json)
		c.Next()
	}
}

// ValidateUUID validates that a URL parameter is a valid UUID
func ValidateUUID(param string) gin.HandlerFunc {
	return func(c *gin.Context) {
		value := c.Param(param)
		if value == "" {
			c.JSON(http.StatusBadRequest, ValidationResponse{
				Error:   "Missing Parameter",
				Message: "Required parameter '" + param + "' is missing",
			})
			c.Abort()
			return
		}

		// Validate UUID format
		if err := validate.Var(value, "uuid"); err != nil {
			c.JSON(http.StatusBadRequest, ValidationResponse{
				Error:   "Invalid Parameter",
				Message: "Parameter '" + param + "' must be a valid UUID",
			})
			c.Abort()
			return
		}

		c.Next()
	}
}

// ValidateQueryParams validates query parameters
func ValidateQueryParams(rules map[string]string) gin.HandlerFunc {
	return func(c *gin.Context) {
		var errors []ValidationError

		for param, rule := range rules {
			value := c.Query(param)
			if value != "" {
				if err := validate.Var(value, rule); err != nil {
					for _, err := range err.(validator.ValidationErrors) {
						errors = append(errors, ValidationError{
							Field:   param,
							Tag:     err.Tag(),
							Value:   err.Param(),
							Message: getErrorMessage(err),
						})
					}
				}
			}
		}

		if len(errors) > 0 {
			c.JSON(http.StatusBadRequest, ValidationResponse{
				Error:   "Invalid Query Parameters",
				Message: "One or more query parameters are invalid",
				Details: errors,
			})
			c.Abort()
			return
		}

		c.Next()
	}
}

// SanitizeInput sanitizes input strings to prevent XSS and injection attacks
func SanitizeInput() gin.HandlerFunc {
	return func(c *gin.Context) {
		// This is a basic implementation - in production you might want to use
		// a more sophisticated sanitization library like bluemonday
		c.Next()
	}
}

// ValidatePermissions validates that permission scopes follow the expected format
func ValidatePermissions(c *gin.Context, permissions []string) []ValidationError {
	var errors []ValidationError
	
	for i, perm := range permissions {
		// Check basic format: should contain only alphanumeric, dots, and underscores
		if err := validate.Var(perm, "required,min=1,max=255,alphanum|contains=.|contains=_"); err != nil {
			errors = append(errors, ValidationError{
				Field:   "permissions[" + string(rune(i)) + "]",
				Tag:     "format",
				Value:   perm,
				Message: "Permission scope must contain only alphanumeric characters, dots, and underscores",
			})
		}

		// Check for dangerous patterns
		if strings.Contains(perm, "..") || strings.HasPrefix(perm, ".") || strings.HasSuffix(perm, ".") {
			errors = append(errors, ValidationError{
				Field:   "permissions[" + string(rune(i)) + "]",
				Tag:     "format",
				Value:   perm,
				Message: "Permission scope has invalid format",
			})
		}
	}
	
	return errors
}
