package handlers

import (
	"encoding/json"
	"net/http"
	"strconv"
	"strings"
	"time"

	"veza-backend-api/internal/errors"

	"github.com/gin-gonic/gin"
	"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
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"`
	HasPrevious    bool   `json:"has_previous"`
	NextCursor     string `json:"next_cursor,omitempty"`
	PreviousCursor string `json:"previous_cursor,omitempty"`
}

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

// ValidationError représente une erreur de validation
type ValidationError struct {
	Field   string `json:"field"`
	Message string `json:"message"`
	Value   string `json:"value,omitempty"`
}

// ValidationErrors représente une liste d'erreurs de validation
type ValidationErrors struct {
	Errors []ValidationError `json:"errors"`
}

// CommonHandler contient les dépendances communes aux handlers
type CommonHandler struct {
	logger *zap.Logger
}

// NewCommonHandler crée une nouvelle instance de CommonHandler
func NewCommonHandler(logger *zap.Logger) *CommonHandler {
	return &CommonHandler{
		logger: logger,
	}
}

// RespondWithSuccess répond avec une réponse de succès
func (h *CommonHandler) RespondWithSuccess(c *gin.Context, data interface{}, message string) {
	response := ResponseData{
		Success:   true,
		Message:   message,
		Data:      data,
		Timestamp: time.Now(),
		RequestID: c.GetString("request_id"),
	}

	c.JSON(http.StatusOK, response)
}

// RespondWithError répond avec une erreur
func (h *CommonHandler) RespondWithError(c *gin.Context, statusCode int, message string, err error) {
	response := ResponseData{
		Success:   false,
		Error:     message,
		Timestamp: time.Now(),
		RequestID: c.GetString("request_id"),
	}

	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),
		)
	}

	c.JSON(statusCode, response)
}

// RespondWithValidationError répond avec des erreurs de validation
func (h *CommonHandler) RespondWithValidationError(c *gin.Context, errors []ValidationError) {
	response := ResponseData{
		Success:   false,
		Error:     "Validation failed",
		Data:      ValidationErrors{Errors: errors},
		Timestamp: time.Now(),
		RequestID: c.GetString("request_id"),
	}

	c.JSON(http.StatusBadRequest, response)
}

// RespondWithPaginatedData répond avec des données paginées
func (h *CommonHandler) RespondWithPaginatedData(c *gin.Context, data interface{}, pagination PaginationData, message string) {
	response := PaginatedResponse{
		ResponseData: ResponseData{
			Success:   true,
			Message:   message,
			Data:      data,
			Timestamp: time.Now(),
			RequestID: c.GetString("request_id"),
		},
		Pagination: pagination,
	}

	c.JSON(http.StatusOK, response)
}

// BindJSON lie les données JSON de la requête à une structure
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
}

// 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 "", errors.NewUnauthorizedError("User not authenticated")
	}

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

	return userIDStr, nil
}

// 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
func (h *CommonHandler) ValidatePagination(page, limit int) []ValidationError {
	var errors []ValidationError

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

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

	return errors
}

// 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
func (h *CommonHandler) ValidateRequiredFields(fields map[string]interface{}) []ValidationError {
	var errors []ValidationError

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

	return errors
}

// 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
}

// MarshalJSON sérialise en JSON de manière sécurisée
func (h *CommonHandler) MarshalJSON(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)
}
