package common

import (
	"encoding/json"
	"errors"
	"fmt"
	"io"
	"net/http"
	"strings"

	"veza-backend-api/internal/response"
	"veza-backend-api/internal/validators"

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

// MaxJSONBodySize définit la taille maximale du body JSON (10MB par défaut)
const MaxJSONBodySize = 10 * 1024 * 1024 // 10MB

// BindAndValidateJSON lie et valide les données JSON de la requête de manière robuste
// MOD-P1-002: Helper centralisé pour bind + validate + format d'erreur standard
// Utilisable par tous les handlers (pas seulement ceux avec CommonHandler)
//
// Comportement:
//   - Vérifie la taille du body (max 10MB)
//   - Parse le JSON avec ShouldBindJSON (Gin)
//   - Valide avec le validator centralisé (go-playground/validator)
//   - Retourne false si erreur (erreur déjà envoyée au client avec code 400)
//
// Usage:
//
//	var req MyRequest
//	if !common.BindAndValidateJSON(c, &req) {
//	    return // Erreur déjà envoyée au client
//	}
func BindAndValidateJSON(c *gin.Context, obj interface{}) bool {
	// 1. Vérifier la taille du body
	if c.Request.ContentLength > MaxJSONBodySize {
		response.BadRequest(c, fmt.Sprintf("Request body too large: maximum size is %d bytes", MaxJSONBodySize))
		return false
	}

	// 2. Limiter la lecture du body pour éviter les attaques par body trop gros
	c.Request.Body = http.MaxBytesReader(c.Writer, c.Request.Body, MaxJSONBodySize)

	// 3. Parser le JSON avec ShouldBindJSON
	if err := c.ShouldBindJSON(obj); err != nil {
		handleBindingError(c, err)
		return false
	}

	// 4. Valider avec le validator centralisé (go-playground/validator)
	validator := validators.NewValidator()
	validationErrors := validator.Validate(obj)
	if len(validationErrors) > 0 {
		// Convertir en format standardisé
		details := make(map[string]string)
		for _, ve := range validationErrors {
			details[ve.Field] = ve.Message
		}
		response.ValidationError(c, "Validation failed", details)
		return false
	}

	return true
}

// BindAndValidate binds the request body to the given object and validates it.
// DEPRECATED: Utiliser BindAndValidateJSON à la place pour une validation complète
// If binding or validation fails, it sends a standardized error response and returns false.
// Returns true if successful.
func BindAndValidate(c *gin.Context, obj interface{}) bool {
	return BindAndValidateJSON(c, obj)
}

// handleBindingError gère les erreurs de binding JSON
// MOD-P1-002: Gestion d'erreurs améliorée pour JSON malformé
func handleBindingError(c *gin.Context, err error) {
	var jsonSyntaxError *json.SyntaxError
	var jsonUnmarshalTypeError *json.UnmarshalTypeError
	var maxBytesError *http.MaxBytesError
	var ve validator.ValidationErrors

	switch {
	case errors.As(err, &maxBytesError):
		// Body trop gros
		response.BadRequest(c, fmt.Sprintf("Request body too large: maximum size is %d bytes", MaxJSONBodySize))

	case errors.As(err, &jsonSyntaxError):
		// JSON syntaxiquement invalide
		response.BadRequest(c, fmt.Sprintf("Invalid JSON syntax at offset %d: %s", jsonSyntaxError.Offset, jsonSyntaxError.Error()))

	case errors.As(err, &jsonUnmarshalTypeError):
		// Type incorrect pour un champ
		response.BadRequest(c, fmt.Sprintf("Invalid type for field '%s': expected %s", jsonUnmarshalTypeError.Field, jsonUnmarshalTypeError.Type.String()))

	case errors.Is(err, io.EOF):
		// Body vide
		response.BadRequest(c, "Request body is empty or invalid JSON")

	case errors.Is(err, io.ErrUnexpectedEOF):
		// JSON incomplet
		response.BadRequest(c, "Incomplete or malformed JSON")

	case errors.As(err, &ve):
		// Erreurs de validation Gin (binding tags)
		details := make(map[string]string)
		for _, fe := range ve {
			details[fe.Field()] = msgForTag(fe)
		}
		response.ValidationError(c, "Validation failed", details)

	default:
		// Erreur générique
		errStr := err.Error()
		if strings.Contains(errStr, "unknown field") {
			response.BadRequest(c, "Unknown fields in JSON payload")
		} else {
			response.BadRequest(c, "Invalid request body format")
		}
	}
}

func msgForTag(fe validator.FieldError) string {
	switch fe.Tag() {
	case "required":
		return "This field is required"
	case "email":
		return "Invalid email format"
	case "min":
		return fmt.Sprintf("Must be at least %s characters long", fe.Param())
	case "max":
		return fmt.Sprintf("Must be at most %s characters long", fe.Param())
	case "uuid":
		return "Invalid UUID format"
	default:
		return fmt.Sprintf("Failed validation on tag: %s", fe.Tag())
	}
}
