package handlers

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

	"veza-backend-api/internal/dto"
	apperrors "veza-backend-api/internal/errors"
	"veza-backend-api/internal/validators"

	"github.com/gin-gonic/gin"
	"github.com/google/uuid"
	"go.uber.org/zap"
)

// ResponseData représente la structure standardisée des réponses API
type ResponseData struct {
	Success   bool        `json:"success"`
	Message   string      `json:"message,omitempty"`
	Data      interface{} `json:"data,omitempty"`
	Error     string      `json:"error,omitempty"`
	Timestamp time.Time   `json:"timestamp"`
	RequestID string      `json:"request_id,omitempty"`
}

// PaginationData représente les données de pagination standardisées
// INT-007: Standardize pagination format with snake_case for consistency
type PaginationData struct {
	Page       int    `json:"page"`
	Limit      int    `json:"limit"`
	Total      int64  `json:"total"`
	TotalPages int    `json:"total_pages"`
	HasNext    bool   `json:"has_next"`
	HasPrev    bool   `json:"has_prev"` // INT-007: Changed from HasPrevious to HasPrev for consistency
	NextCursor string `json:"next_cursor,omitempty"`
	PrevCursor string `json:"prev_cursor,omitempty"` // INT-007: Changed from PreviousCursor to PrevCursor for consistency
}

// PaginatedResponse représente une réponse paginée
type PaginatedResponse struct {
	ResponseData
	Pagination PaginationData `json:"pagination"`
}

// ValidationError et ValidationErrors sont maintenant dans internal/dto/validation.go
// pour éviter les cycles d'import. Utiliser dto.ValidationError et dto.ValidationErrors

// CommonHandler contient les dépendances communes aux handlers
type CommonHandler struct {
	logger    *zap.Logger
	validator *validators.Validator // GO-013: Validator centralisé
}

// NewCommonHandler crée une nouvelle instance de CommonHandler
// GO-013: Initialise le validator centralisé
func NewCommonHandler(logger *zap.Logger) *CommonHandler {
	return &CommonHandler{
		logger:    logger,
		validator: validators.NewValidator(),
	}
}

// ValidateRequest valide une requête avec le validator centralisé
// GO-013: Helper pour valider les requêtes et retourner des erreurs formatées
func (h *CommonHandler) ValidateRequest(c *gin.Context, req interface{}) bool {
	validationErrors := h.validator.Validate(req)
	if len(validationErrors) > 0 {
		h.RespondWithValidationError(c, validationErrors)
		return false
	}
	return true
}

// RespondWithSuccess répond avec une réponse de succès
func (h *CommonHandler) RespondWithSuccess(c *gin.Context, data interface{}, message string) {
	// Utiliser la structure unifiée APIResponse via RespondSuccess
	// Si message est présent, on l'encapsule avec les données
	if message != "" {
		RespondSuccess(c, http.StatusOK, gin.H{
			"message": message,
			"data":    data,
		})
	} else {
		RespondSuccess(c, http.StatusOK, data)
	}
}

// RespondWithError répond avec une erreur
func (h *CommonHandler) RespondWithError(c *gin.Context, statusCode int, message string, err error) {
	// Utiliser la structure unifiée APIResponse
	// On crée une structure d'erreur ad-hoc pour correspondre à l'interface attendue par APIResponse.Error (qui est interface{})
	// Ou mieux, on utilise RespondWithError qui attend un code, message et détails

	// Note: RespondWithError est defined in error_response.go et attend (c, code, message, details...)
	// Ici on a statusCode HTTP. RespondWithError attend un ErrorCode interne.
	// C'est un conflit de signature.
	// On va donc construire manuellement la réponse d'erreur unifiée.

	errResponse := gin.H{
		"code":    statusCode,
		"message": message,
		"details": nil,
	}
	if err != nil {
		h.logger.Error("Handler error",
			zap.String("error", err.Error()),
			zap.String("request_id", c.GetString("request_id")),
			zap.String("endpoint", c.Request.URL.Path),
		)
		// On pourrait ajouter err.Error() dans details, mais pour sécurité on évite d'exposer l'erreur brute sauf si nécessaire
	}

	c.JSON(statusCode, APIResponse{
		Success: false,
		Data:    nil,
		Error:   errResponse,
	})
}

// RespondWithValidationError répond avec des erreurs de validation
// GO-013: Utilise dto.ValidationError pour éviter les cycles d'import
func (h *CommonHandler) RespondWithValidationError(c *gin.Context, errors []dto.ValidationError) {
	// Adapter pour l'enveloppe unifiée
	// Code 400 ou 422

	c.JSON(http.StatusBadRequest, APIResponse{
		Success: false,
		Data:    nil,
		Error: gin.H{
			"code":    http.StatusBadRequest,
			"message": "Validation failed",
			"details": errors,
		},
	})
}

// RespondWithPaginatedData répond avec des données paginées
// INT-007: Standardize pagination response format
func (h *CommonHandler) RespondWithPaginatedData(c *gin.Context, data interface{}, pagination PaginationData, message string) {
	// Pour la pagination, on met tout dans Data
	responseData := gin.H{
		"list":       data,
		"pagination": pagination,
	}
	if message != "" {
		responseData["message"] = message
	}

	RespondSuccess(c, http.StatusOK, responseData)
}

// BuildPaginationData construit une PaginationData standardisée à partir des paramètres
// INT-007: Helper pour standardiser la création de PaginationData
func BuildPaginationData(page, limit int, total int64) PaginationData {
	totalPages := int((total + int64(limit) - 1) / int64(limit))
	if totalPages == 0 {
		totalPages = 1
	}

	return PaginationData{
		Page:       page,
		Limit:      limit,
		Total:      total,
		TotalPages: totalPages,
		HasNext:    page < totalPages,
		HasPrev:    page > 1,
	}
}

// BuildPaginationDataWithCursor construit une PaginationData avec curseurs
// INT-007: Helper pour pagination cursor-based
func BuildPaginationDataWithCursor(limit int, total int64, nextCursor, prevCursor string) PaginationData {
	// Pour cursor-based, on ne peut pas calculer totalPages exactement
	// mais on peut indiquer s'il y a une page suivante/précédente
	hasNext := nextCursor != ""
	hasPrev := prevCursor != ""

	return PaginationData{
		Limit:      limit,
		Total:      total,
		HasNext:    hasNext,
		HasPrev:    hasPrev,
		NextCursor: nextCursor,
		PrevCursor: prevCursor,
	}
}

// BindJSON lie les données JSON de la requête à une structure
// DEPRECATED: Utiliser BindAndValidateJSON à la place pour une gestion d'erreurs robuste
func (h *CommonHandler) BindJSON(c *gin.Context, obj interface{}) error {
	if err := c.ShouldBindJSON(obj); err != nil {
		h.logger.Warn("Failed to bind JSON",
			zap.Error(err),
			zap.String("request_id", c.GetString("request_id")),
		)
		return err
	}
	return nil
}

// 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
// P0: JSON Hardening - Garantit qu'aucune erreur de parsing/validation ne passe silencieusement
//
// Comportement:
//   - Vérifie la taille du body (max 10MB par défaut)
//   - Parse le JSON avec ShouldBindJSON (Gin)
//   - Valide avec le validator centralisé
//   - Retourne une AppError avec code approprié (400 pour JSON malformé, 422 pour validation)
//
// Usage:
//
//	var req MyRequest
//	if appErr := h.BindAndValidateJSON(c, &req); appErr != nil {
//	    RespondWithAppError(c, appErr)
//	    return
//	}
func (h *CommonHandler) BindAndValidateJSON(c *gin.Context, obj interface{}) *apperrors.AppError {
	requestID := c.GetString("request_id")

	// 1. Vérifier la taille du body
	if c.Request.ContentLength > MaxJSONBodySize {
		h.logger.Warn("Request body too large",
			zap.Int64("content_length", c.Request.ContentLength),
			zap.Int64("max_size", MaxJSONBodySize),
			zap.String("request_id", requestID),
			zap.String("endpoint", c.Request.URL.Path),
		)
		return apperrors.New(
			apperrors.ErrCodeValidation,
			fmt.Sprintf("Request body too large: maximum size is %d bytes", MaxJSONBodySize),
		)
	}

	// 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 {
		// Analyser le type d'erreur pour retourner le bon code
		var jsonSyntaxError *json.SyntaxError
		var jsonUnmarshalTypeError *json.UnmarshalTypeError
		var maxBytesError *http.MaxBytesError

		switch {
		case errors.As(err, &maxBytesError):
			// Body trop gros (dépassement de la limite)
			h.logger.Warn("Request body exceeds maximum size",
				zap.Error(err),
				zap.String("request_id", requestID),
				zap.String("endpoint", c.Request.URL.Path),
			)
			return apperrors.New(
				apperrors.ErrCodeValidation,
				fmt.Sprintf("Request body too large: maximum size is %d bytes", MaxJSONBodySize),
			)

		case errors.As(err, &jsonSyntaxError):
			// JSON syntaxiquement invalide
			h.logger.Warn("Invalid JSON syntax",
				zap.Error(err),
				zap.Int64("offset", jsonSyntaxError.Offset),
				zap.String("request_id", requestID),
				zap.String("endpoint", c.Request.URL.Path),
			)
			return apperrors.New(
				apperrors.ErrCodeValidation,
				fmt.Sprintf("Invalid JSON syntax at offset %d: %s", jsonSyntaxError.Offset, jsonSyntaxError.Error()),
			)

		case errors.As(err, &jsonUnmarshalTypeError):
			// Type incorrect pour un champ
			h.logger.Warn("Invalid JSON type",
				zap.Error(err),
				zap.String("field", jsonUnmarshalTypeError.Field),
				zap.String("type", jsonUnmarshalTypeError.Type.String()),
				zap.String("request_id", requestID),
				zap.String("endpoint", c.Request.URL.Path),
			)
			return apperrors.New(
				apperrors.ErrCodeInvalidFormat,
				fmt.Sprintf("Invalid type for field '%s': expected %s", jsonUnmarshalTypeError.Field, jsonUnmarshalTypeError.Type.String()),
			)

		case errors.Is(err, io.EOF):
			// Body vide
			h.logger.Warn("Empty request body",
				zap.String("request_id", requestID),
				zap.String("endpoint", c.Request.URL.Path),
			)
			return apperrors.New(
				apperrors.ErrCodeValidation,
				"Request body is empty or invalid JSON",
			)

		case errors.Is(err, io.ErrUnexpectedEOF):
			// JSON incomplet
			h.logger.Warn("Incomplete JSON",
				zap.Error(err),
				zap.String("request_id", requestID),
				zap.String("endpoint", c.Request.URL.Path),
			)
			return apperrors.New(
				apperrors.ErrCodeValidation,
				"Incomplete or malformed JSON",
			)

		default:
			// Erreur générique de binding (peut inclure des erreurs de validation Gin)
			// On va laisser le validator gérer les erreurs de validation
			// Si c'est une erreur de binding Gin (ex: unknown field), on la traite ici
			errStr := err.Error()
			if strings.Contains(errStr, "unknown field") || strings.Contains(errStr, "unknown") {
				h.logger.Warn("Unknown fields in JSON",
					zap.Error(err),
					zap.String("request_id", requestID),
					zap.String("endpoint", c.Request.URL.Path),
				)
				return apperrors.New(
					apperrors.ErrCodeValidation,
					"Unknown fields in JSON payload",
				)
			}

			// Pour les autres erreurs de binding, on considère que c'est une erreur de validation
			// Les erreurs de validation de binding Gin (comme "required", "url", "min") doivent être retournées
			h.logger.Debug("JSON binding error (will be handled by validator)",
				zap.Error(err),
				zap.String("request_id", requestID),
				zap.String("endpoint", c.Request.URL.Path),
			)
			// Améliorer le message d'erreur pour les validations courantes
			errMsg := err.Error()
			if strings.Contains(errMsg, "Password") && strings.Contains(errMsg, "min") {
				errMsg = "Le mot de passe doit contenir au moins 12 caractères"
			} else if strings.Contains(errMsg, "PasswordConfirm") && strings.Contains(errMsg, "eqfield") {
				errMsg = "Les mots de passe ne correspondent pas"
			} else if strings.Contains(errMsg, "Email") && strings.Contains(errMsg, "email") {
				errMsg = "Format d'email invalide"
			} else if strings.Contains(errMsg, "Username") && strings.Contains(errMsg, "min") {
				errMsg = "Le nom d'utilisateur doit contenir au moins 3 caractères"
			} else if strings.Contains(errMsg, "required") {
				// Extraire le nom du champ
				if strings.Contains(errMsg, "Password") {
					errMsg = "Le mot de passe est requis"
				} else if strings.Contains(errMsg, "Email") {
					errMsg = "L'email est requis"
				} else if strings.Contains(errMsg, "PasswordConfirm") {
					errMsg = "La confirmation du mot de passe est requise"
				} else if strings.Contains(errMsg, "Username") {
					errMsg = "Le nom d'utilisateur est requis"
				} else {
					errMsg = fmt.Sprintf("Validation failed: %s", errMsg)
				}
			} else {
				errMsg = fmt.Sprintf("Validation failed: %s", errMsg)
			}

			// Retourner l'erreur de validation de binding
			return apperrors.New(
				apperrors.ErrCodeValidation,
				errMsg,
			)
		}
	}

	// 4. Valider avec le validator centralisé
	validationErrors := h.validator.Validate(obj)
	if len(validationErrors) > 0 {
		// Convertir dto.ValidationError en errors.ErrorDetail
		details := make([]apperrors.ErrorDetail, 0, len(validationErrors))
		for _, ve := range validationErrors {
			details = append(details, apperrors.ErrorDetail{
				Field:   ve.Field,
				Message: ve.Message,
			})
		}

		h.logger.Warn("Validation failed",
			zap.Int("error_count", len(validationErrors)),
			zap.String("request_id", requestID),
			zap.String("endpoint", c.Request.URL.Path),
		)

		return apperrors.NewValidationError("Validation failed", details...)
	}

	return nil
}

// GetUserIDFromContext extrait l'ID utilisateur du contexte
func (h *CommonHandler) GetUserIDFromContext(c *gin.Context) (string, error) {
	userID, exists := c.Get("user_id")
	if !exists {
		return "", apperrors.NewUnauthorizedError("User not authenticated")
	}

	userIDStr, ok := userID.(string)
	if !ok {
		return "", apperrors.New(apperrors.ErrCodeValidation, "Invalid user ID type")
	}

	return userIDStr, nil
}

// GetUserIDUUID extrait l'ID utilisateur du contexte comme uuid.UUID (MOD-P1-001)
// Retourne false si user_id est absent ou invalide (répond déjà avec 401)
func GetUserIDUUID(c *gin.Context) (uuid.UUID, bool) {
	userIDInterface, exists := c.Get("user_id")
	if !exists {
		RespondWithAppError(c, apperrors.NewUnauthorizedError("unauthorized"))
		return uuid.Nil, false
	}

	userID, ok := userIDInterface.(uuid.UUID)
	if !ok {
		RespondWithAppError(c, apperrors.NewUnauthorizedError("unauthorized"))
		return uuid.Nil, false
	}

	return userID, true
}

// WithTimeout crée un context avec timeout pour les opérations I/O critiques (MOD-P1-004)
// Utilise le timeout par défaut de 5s pour DB/Redis, ou le timeout fourni
func WithTimeout(ctx context.Context, timeout time.Duration) (context.Context, context.CancelFunc) {
	if timeout == 0 {
		timeout = 5 * time.Second // Default timeout pour DB/Redis
	}
	return context.WithTimeout(ctx, timeout)
}

// GetPaginationParams extrait les paramètres de pagination de la requête
func (h *CommonHandler) GetPaginationParams(c *gin.Context) (page, limit int, cursor string) {
	page = 1
	limit = 20

	if pageStr := c.Query("page"); pageStr != "" {
		if p, err := strconv.Atoi(pageStr); err == nil && p > 0 {
			page = p
		}
	}

	if limitStr := c.Query("limit"); limitStr != "" {
		if l, err := strconv.Atoi(limitStr); err == nil && l > 0 && l <= 100 {
			limit = l
		}
	}

	cursor = c.Query("cursor")
	return page, limit, cursor
}

// ValidatePagination valide les paramètres de pagination
// GO-013: Utilise dto.ValidationError
func (h *CommonHandler) ValidatePagination(page, limit int) []dto.ValidationError {
	var validationErrors []dto.ValidationError

	if page < 1 {
		validationErrors = append(validationErrors, dto.ValidationError{
			Field:   "page",
			Message: "Page must be greater than 0",
			Value:   strconv.Itoa(page),
		})
	}

	if limit < 1 || limit > 100 {
		validationErrors = append(validationErrors, dto.ValidationError{
			Field:   "limit",
			Message: "Limit must be between 1 and 100",
			Value:   strconv.Itoa(limit),
		})
	}

	return validationErrors
}

// LogRequest log une requête entrante
func (h *CommonHandler) LogRequest(c *gin.Context, operation string) {
	h.logger.Info("Request received",
		zap.String("method", c.Request.Method),
		zap.String("path", c.Request.URL.Path),
		zap.String("operation", operation),
		zap.String("user_id", c.GetString("user_id")),
		zap.String("request_id", c.GetString("request_id")),
		zap.String("ip", c.ClientIP()),
		zap.String("user_agent", c.Request.UserAgent()),
	)
}

// LogResponse log une réponse sortante
func (h *CommonHandler) LogResponse(c *gin.Context, statusCode int, duration time.Duration) {
	h.logger.Info("Response sent",
		zap.Int("status_code", statusCode),
		zap.Duration("duration", duration),
		zap.String("request_id", c.GetString("request_id")),
	)
}

// SetRequestID middleware pour ajouter un ID de requête
func (h *CommonHandler) SetRequestID() gin.HandlerFunc {
	return func(c *gin.Context) {
		requestID := c.GetHeader("X-Request-ID")
		if requestID == "" {
			requestID = generateRequestID()
		}
		c.Set("request_id", requestID)
		c.Header("X-Request-ID", requestID)
		c.Next()
	}
}

// generateRequestID génère un ID de requête unique
func generateRequestID() string {
	return strconv.FormatInt(time.Now().UnixNano(), 36)
}

// ValidateRequiredFields valide que les champs requis sont présents
// GO-013: Utilise dto.ValidationError
func (h *CommonHandler) ValidateRequiredFields(fields map[string]interface{}) []dto.ValidationError {
	var validationErrors []dto.ValidationError

	for field, value := range fields {
		if value == nil || value == "" {
			validationErrors = append(validationErrors, dto.ValidationError{
				Field:   field,
				Message: "This field is required",
			})
		}
	}

	return validationErrors
}

// SanitizeString nettoie une chaîne de caractères
func (h *CommonHandler) SanitizeString(input string) string {
	// Supprimer les caractères de contrôle et les espaces en début/fin
	cleaned := strings.TrimSpace(input)

	// Limiter la longueur
	if len(cleaned) > 1000 {
		cleaned = cleaned[:1000]
	}

	return cleaned
}

// ParseJSON parse du JSON de manière sécurisée
func (h *CommonHandler) ParseJSON(data []byte, v interface{}) error {
	if err := json.Unmarshal(data, v); err != nil {
		h.logger.Error("Failed to parse JSON", zap.Error(err))
		return err
	}
	return nil
}

// SafeMarshalJSON sérialise en JSON de manière sécurisée
func (h *CommonHandler) SafeMarshalJSON(v interface{}) ([]byte, error) {
	data, err := json.Marshal(v)
	if err != nil {
		h.logger.Error("Failed to marshal JSON", zap.Error(err))
		return nil, err
	}
	return data, nil
}

// GetClientIP obtient l'IP réelle du client
func (h *CommonHandler) GetClientIP(c *gin.Context) string {
	// Vérifier les headers de proxy
	if ip := c.GetHeader("X-Forwarded-For"); ip != "" {
		return strings.Split(ip, ",")[0]
	}
	if ip := c.GetHeader("X-Real-IP"); ip != "" {
		return ip
	}
	return c.ClientIP()
}

// RateLimitKey génère une clé pour le rate limiting
func (h *CommonHandler) RateLimitKey(c *gin.Context, prefix string) string {
	userID := c.GetString("user_id")
	if userID != "" {
		return prefix + ":user:" + userID
	}
	return prefix + ":ip:" + h.GetClientIP(c)
}
