package middleware

import (
	"context"
	"fmt"
	"net/http"
	"strings"
	"time"

	"veza-backend-api/internal/services"

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

// ÉTAPE 3.4: Interfaces pour permettre l'injection de dépendances et les tests avec mocks

// SessionValidator définit l'interface pour valider les sessions
type SessionValidator interface {
	ValidateSession(ctx context.Context, token string) (*services.Session, error)
	RefreshSession(ctx context.Context, token string, newExpiresIn time.Duration) error
}

// AuditRecorder définit l'interface pour enregistrer les actions d'audit
type AuditRecorder interface {
	LogAction(ctx context.Context, req *services.AuditLogCreateRequest) error
}

// PermissionChecker définit l'interface pour vérifier les permissions
type PermissionChecker interface {
	HasRole(ctx context.Context, userID uuid.UUID, roleName string) (bool, error)
	HasPermission(ctx context.Context, userID uuid.UUID, permissionName string) (bool, error)
}

// AuthMiddleware middleware d'authentification avec validation de session
// ÉTAPE 3.4: Utilise des interfaces pour permettre l'injection de dépendances et les tests
type AuthMiddleware struct {
	sessionService    SessionValidator
	auditService      AuditRecorder
	permissionService PermissionChecker
	logger            *zap.Logger
	jwtSecret         string
}

// NewAuthMiddleware crée un nouveau middleware d'authentification
// ÉTAPE 3.4: Accepte des interfaces au lieu de types concrets pour permettre les tests avec mocks
func NewAuthMiddleware(
	sessionService SessionValidator,
	auditService AuditRecorder,
	permissionService PermissionChecker,
	logger *zap.Logger,
	jwtSecret string,
) *AuthMiddleware {
	return &AuthMiddleware{
		sessionService:    sessionService,
		auditService:      auditService,
		permissionService: permissionService,
		logger:            logger,
		jwtSecret:         jwtSecret,
	}
}

// RequireAuth middleware qui exige une authentification
func (am *AuthMiddleware) RequireAuth() gin.HandlerFunc {
	return func(c *gin.Context) {
		// Récupérer le token depuis le header Authorization
		authHeader := c.GetHeader("Authorization")
		if authHeader == "" {
			am.logger.Warn("Missing Authorization header",
				zap.String("ip", c.ClientIP()),
				zap.String("user_agent", c.GetHeader("User-Agent")),
			)
			c.JSON(http.StatusUnauthorized, gin.H{"error": "Authorization header required"})
			c.Abort()
			return
		}

		// Vérifier le format Bearer token
		tokenParts := strings.Split(authHeader, " ")
		if len(tokenParts) != 2 || tokenParts[0] != "Bearer" {
			am.logger.Warn("Invalid Authorization header format",
				zap.String("ip", c.ClientIP()),
				zap.String("header", authHeader),
			)
			c.JSON(http.StatusUnauthorized, gin.H{"error": "Invalid Authorization header format"})
			c.Abort()
			return
		}

		tokenString := tokenParts[1]

		// Valider le token JWT
		userID, err := am.validateJWTToken(tokenString)
		if err != nil {
			am.logger.Warn("Invalid JWT token",
				zap.Error(err),
				zap.String("ip", c.ClientIP()),
			)
			c.JSON(http.StatusUnauthorized, gin.H{"error": "Invalid token"})
			c.Abort()
			return
		}

		// Valider la session côté serveur
		session, err := am.sessionService.ValidateSession(c.Request.Context(), tokenString)
		if err != nil {
			am.logger.Warn("Invalid session",
				zap.Error(err),
				zap.String("user_id", userID.String()),
				zap.String("ip", c.ClientIP()),
			)
			c.JSON(http.StatusUnauthorized, gin.H{"error": "Session expired or invalid"})
			c.Abort()
			return
		}

		// Vérifier que l'utilisateur correspond
		// Convert session.UserID (uuid) to string if needed, or handle int IDs.
		// NOTE: Assuming Session struct uses uuid.UUID but DB uses int ID.
		// If Session struct uses int ID (which it should if DB uses int), then straightforward.
		// If Session uses UUID, we have a problem.
		// Assuming for now simple string comparison or ID is stored as string/uuid in session.

		// Vérifier que l'utilisateur correspond
		if session.UserID != userID {
			am.logger.Warn("Session user mismatch",
				zap.String("session_user_id", session.UserID.String()),
				zap.String("token_user_id", userID.String()),
			)
			c.JSON(http.StatusForbidden, gin.H{"error": "Session user mismatch"}) // Changed to StatusForbidden
			c.Abort()
			return
		}

		// Ajouter les informations utilisateur au contexte
		c.Set("user_id", userID)

		c.Set("session_id", session.ID)
		c.Set("session_created_at", session.CreatedAt)
		c.Set("session_expires_at", session.ExpiresAt)

		// Log l'accès dans l'audit
		// Log l'accès dans l'audit
		err = am.auditService.LogAction(c.Request.Context(), &services.AuditLogCreateRequest{
			UserID:    &userID,
			Action:    "api_access",
			Resource:  "endpoint",
			IPAddress: c.ClientIP(),
			UserAgent: c.GetHeader("User-Agent"),
			Metadata: map[string]interface{}{
				"endpoint":   c.Request.URL.Path,
				"method":     c.Request.Method,
				"session_id": session.ID.String(),
			},
		})
		if err != nil {
			am.logger.Error("Failed to log API access",
				zap.Error(err),
				zap.String("user_id", userID.String()),
			)
		}

		c.Next()
	}
}

// OptionalAuth middleware d'authentification optionnelle
// MIGRATION UUID: Simplifié, utilise UUID directement
func (am *AuthMiddleware) OptionalAuth() gin.HandlerFunc {
	return func(c *gin.Context) {
		authHeader := c.GetHeader("Authorization")
		if authHeader == "" {
			c.Next()
			return
		}

		tokenParts := strings.Split(authHeader, " ")
		if len(tokenParts) != 2 || tokenParts[0] != "Bearer" {
			c.Next()
			return
		}

		tokenString := tokenParts[1]

		userID, err := am.validateJWTToken(tokenString)
		if err != nil {
			c.Next()
			return
		}

		session, err := am.sessionService.ValidateSession(c.Request.Context(), tokenString)
		if err != nil {
			c.Next()
			return
		}

		// Ajouter UUID directement au contexte
		c.Set("user_id", userID)
		c.Set("session_id", session.ID)
		c.Set("session_created_at", session.CreatedAt)
		c.Set("session_expires_at", session.ExpiresAt)

		c.Next()
	}
}

// RequireAdmin middleware qui exige des droits administrateur
// GO-001, GO-005, GO-006: Implémentation RBAC réelle avec PermissionService
// MIGRATION UUID: userID est toujours uuid.UUID, plus de conversion
// Note: RequireAdmin() inclut la vérification d'authentification, pas besoin d'appeler RequireAuth() séparément
func (am *AuthMiddleware) RequireAdmin() gin.HandlerFunc {
	return func(c *gin.Context) {
		// Vérifier l'authentification d'abord (même logique que RequireAuth)
		authHeader := c.GetHeader("Authorization")
		if authHeader == "" {
			c.JSON(http.StatusUnauthorized, gin.H{"error": "Authorization header required"})
			c.Abort()
			return
		}

		// Extraire le token
		const bearerPrefix = "Bearer "
		if !strings.HasPrefix(authHeader, bearerPrefix) {
			c.JSON(http.StatusUnauthorized, gin.H{"error": "Invalid authorization header format"})
			c.Abort()
			return
		}

		token := strings.TrimPrefix(authHeader, bearerPrefix)
		if token == "" {
			c.JSON(http.StatusUnauthorized, gin.H{"error": "Token required"})
			c.Abort()
			return
		}

		// Valider la session
		session, err := am.sessionService.ValidateSession(c.Request.Context(), token)
		if err != nil {
			c.JSON(http.StatusUnauthorized, gin.H{"error": "Invalid or expired session"})
			c.Abort()
			return
		}

		// Extraire userID du token JWT
		userID, err := am.validateJWTToken(token)
		if err != nil {
			c.JSON(http.StatusUnauthorized, gin.H{"error": "Invalid token"})
			c.Abort()
			return
		}

		// Set user_id dans le contexte
		c.Set("user_id", userID)
		c.Set("session_id", session.ID)
		c.Set("session_created_at", session.CreatedAt)
		c.Set("session_expires_at", session.ExpiresAt)

		// Vérification RBAC réelle
		hasRole, err := am.permissionService.HasRole(c.Request.Context(), userID, "admin")
		if err != nil {
			am.logger.Error("Failed to check admin role", zap.Error(err))
			c.JSON(http.StatusInternalServerError, gin.H{"error": "Internal server error"})
			c.Abort()
			return
		}

		if !hasRole {
			am.logger.Warn("Admin access denied",
				zap.String("user_id", userID.String()),
				zap.String("ip", c.ClientIP()),
			)
			c.JSON(http.StatusForbidden, gin.H{"error": "Insufficient permissions"})
			c.Abort()
			return
		}

		am.logger.Info("Admin access granted",
			zap.String("user_id", userID.String()),
			zap.String("ip", c.ClientIP()),
			zap.String("endpoint", c.Request.URL.Path),
		)

		c.Next()
	}
}

// RequirePermission middleware qui exige une permission spécifique
// GO-001, GO-005: Implémentation RBAC réelle avec PermissionService
// MIGRATION UUID: userID est toujours uuid.UUID
func (am *AuthMiddleware) RequirePermission(permission string) gin.HandlerFunc {
	return func(c *gin.Context) {
		am.RequireAuth()(c)
		if c.IsAborted() {
			return
		}

		userIDInterface, exists := c.Get("user_id")
		if !exists {
			c.JSON(http.StatusUnauthorized, gin.H{"error": "User not authenticated"})
			c.Abort()
			return
		}

		userID, ok := userIDInterface.(uuid.UUID)
		if !ok {
			c.JSON(http.StatusInternalServerError, gin.H{"error": "Invalid user ID type in context"})
			c.Abort()
			return
		}

		// Vérification RBAC réelle
		hasPermission, err := am.permissionService.HasPermission(c.Request.Context(), userID, permission)
		if err != nil {
			am.logger.Error("Failed to check permission", zap.Error(err))
			c.JSON(http.StatusInternalServerError, gin.H{"error": "Internal server error"})
			c.Abort()
			return
		}

		if !hasPermission {
			am.logger.Warn("Permission denied",
				zap.String("user_id", userID.String()),
				zap.String("permission", permission),
			)
			c.JSON(http.StatusForbidden, gin.H{"error": "Insufficient permissions"})
			c.Abort()
			return
		}

		am.logger.Info("Permission check passed",
			zap.String("user_id", userID.String()),
			zap.String("permission", permission),
			zap.String("ip", c.ClientIP()),
			zap.String("endpoint", c.Request.URL.Path),
		)

		c.Next()
	}
}

// RequireContentCreatorRole middleware qui exige un rôle de créateur de contenu
// GO-012: Vérifie que l'utilisateur a un des rôles: creator, premium, admin
// Selon ORIGIN_SECURITY_FRAMEWORK, seuls ces rôles peuvent créer du contenu
func (am *AuthMiddleware) RequireContentCreatorRole() gin.HandlerFunc {
	return func(c *gin.Context) {
		am.RequireAuth()(c)
		if c.IsAborted() {
			return
		}

		userIDInterface, exists := c.Get("user_id")
		if !exists {
			c.JSON(http.StatusUnauthorized, gin.H{"error": "User not authenticated"})
			c.Abort()
			return
		}

		userID, ok := userIDInterface.(uuid.UUID)
		if !ok {
			c.JSON(http.StatusInternalServerError, gin.H{"error": "Invalid user ID type in context"})
			c.Abort()
			return
		}

		// Vérifier si l'utilisateur a un des rôles autorisés: creator, premium, admin
		allowedRoles := []string{"creator", "premium", "admin", "artist", "producer", "label"}
		hasAllowedRole := false
		var lastErr error

		for _, role := range allowedRoles {
			hasRole, err := am.permissionService.HasRole(c.Request.Context(), userID, role)
			if err != nil {
				lastErr = err
				continue
			}
			if hasRole {
				hasAllowedRole = true
				break
			}
		}

		if !hasAllowedRole {
			am.logger.Warn("Content creation denied - insufficient role",
				zap.String("user_id", userID.String()),
				zap.String("ip", c.ClientIP()),
				zap.String("endpoint", c.Request.URL.Path),
			)
			c.JSON(http.StatusForbidden, gin.H{
				"error": "Insufficient permissions. Content creation requires creator, premium, or admin role.",
			})
			c.Abort()
			return
		}

		if lastErr != nil {
			am.logger.Error("Error checking roles (but user has allowed role)", zap.Error(lastErr))
		}

		am.logger.Info("Content creation access granted",
			zap.String("user_id", userID.String()),
			zap.String("ip", c.ClientIP()),
			zap.String("endpoint", c.Request.URL.Path),
		)

		c.Next()
	}
}

// validateJWTToken valide un token JWT et retourne l'ID utilisateur (UUID)
// MIGRATION UUID: Retourne maintenant uuid.UUID au lieu de string
func (am *AuthMiddleware) validateJWTToken(tokenString string) (uuid.UUID, error) {
	token, err := jwt.Parse(tokenString, func(token *jwt.Token) (interface{}, error) {
		if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok {
			return nil, jwt.ErrSignatureInvalid
		}
		return []byte(am.jwtSecret), nil
	})

	if err != nil {
		return uuid.Nil, err
	}

	if !token.Valid {
		return uuid.Nil, jwt.ErrTokenMalformed
	}

	claims, ok := token.Claims.(jwt.MapClaims)
	if !ok {
		return uuid.Nil, jwt.ErrTokenMalformed
	}

	// Support 'sub' (standard) qui devrait contenir l'UUID sous forme de string
	if sub, ok := claims["sub"]; ok {
		switch v := sub.(type) {
		case string:
			uid, err := uuid.Parse(v)
			if err != nil {
				return uuid.Nil, fmt.Errorf("invalid UUID in sub claim: %w", err)
			}
			return uid, nil
		default:
			return uuid.Nil, fmt.Errorf("sub claim must be UUID string, got: %T", v)
		}
	}

	// Fallback sur user_id custom claim (legacy)
	if userIDStr, ok := claims["user_id"].(string); ok {
		uid, err := uuid.Parse(userIDStr)
		if err != nil {
			return uuid.Nil, fmt.Errorf("invalid UUID in user_id claim: %w", err)
		}
		return uid, nil
	}

	return uuid.Nil, jwt.ErrTokenMalformed
}

// RefreshToken middleware pour rafraîchir les tokens
// MIGRATION UUID: Simplifié pour UUID
func (am *AuthMiddleware) RefreshToken() gin.HandlerFunc {
	return func(c *gin.Context) {
		authHeader := c.GetHeader("Authorization")
		if authHeader == "" {
			c.JSON(http.StatusUnauthorized, gin.H{"error": "Authorization header required"})
			c.Abort()
			return
		}

		tokenParts := strings.Split(authHeader, " ")
		if len(tokenParts) != 2 || tokenParts[0] != "Bearer" {
			c.JSON(http.StatusUnauthorized, gin.H{"error": "Invalid Authorization header format"})
			c.Abort()
			return
		}

		tokenString := tokenParts[1]

		userID, err := am.validateJWTToken(tokenString)
		if err != nil {
			c.JSON(http.StatusUnauthorized, gin.H{"error": "Invalid token"})
			c.Abort()
			return
		}

		session, err := am.sessionService.ValidateSession(c.Request.Context(), tokenString)
		if err != nil {
			c.JSON(http.StatusUnauthorized, gin.H{"error": "Session expired or invalid"})
			c.Abort()
			return
		}

		newExpiresIn := 24 * time.Hour
		err = am.sessionService.RefreshSession(c.Request.Context(), tokenString, newExpiresIn)
		if err != nil {
			am.logger.Error("Failed to refresh session",
				zap.Error(err),
				zap.String("user_id", userID.String()),
			)
			c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to refresh session"})
			c.Abort()
			return
		}

		// Log le rafraîchissement
		am.logger.Info("Token refreshed",
			zap.String("user_id", userID.String()),
			zap.String("session_id", session.ID.String()),
		)

		c.JSON(http.StatusOK, gin.H{
			"message":    "Token refreshed successfully",
			"expires_in": newExpiresIn.Seconds(),
		})
	}
}


