package middleware

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

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

	"github.com/gin-gonic/gin"
	"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
	jwtService        *services.JWTService  // T0204: Use JWTService for validation
	userService       *services.UserService // T0204: Check TokenVersion
	logger            *zap.Logger
}

// 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,
	jwtService *services.JWTService,
	userService *services.UserService,
	logger *zap.Logger,
) *AuthMiddleware {
	return &AuthMiddleware{
		sessionService:    sessionService,
		auditService:      auditService,
		permissionService: permissionService,
		jwtService:        jwtService,
		userService:       userService,
		logger:            logger,
	}
}

// authenticate performs the core authentication logic
// Returns userID and true if successful, otherwise handles error response and returns false
func (am *AuthMiddleware) authenticate(c *gin.Context) (uuid.UUID, bool) {
	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")),
		)
		response.Unauthorized(c, "Authorization header required")
		c.Abort()
		return uuid.Nil, false
	}

	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),
		)
		response.Unauthorized(c, "Invalid Authorization header format")
		c.Abort()
		return uuid.Nil, false
	}

	tokenString := tokenParts[1]

	// T0204: Validate token using JWTService (checks sig, exp, iss, aud, alg)
	claims, err := am.jwtService.ValidateToken(tokenString)
	if err != nil {
		am.logger.Warn("Invalid JWT token",
			zap.Error(err),
			zap.String("ip", c.ClientIP()),
		)
		response.Unauthorized(c, "Invalid token")
		c.Abort()
		return uuid.Nil, false
	}

	userID := claims.UserID

	// T0204: Check TokenVersion against DB to ensure immediate revocation
	user, err := am.userService.GetByID(userID)
	if err != nil {
		am.logger.Warn("User not found during auth",
			zap.Error(err),
			zap.String("user_id", userID.String()),
		)
		response.Unauthorized(c, "User not found")
		c.Abort()
		return uuid.Nil, false
	}

	if err := am.jwtService.VerifyTokenVersion(claims, user.TokenVersion); err != nil {
		am.logger.Warn("Token version mismatch (revoked)",
			zap.Error(err),
			zap.String("user_id", userID.String()),
			zap.Int("token_version", claims.TokenVersion),
			zap.Int("user_version", user.TokenVersion),
		)
		response.Unauthorized(c, "Token revoked")
		c.Abort()
		return uuid.Nil, false
	}

	session, err := am.sessionService.ValidateSession(c.Request.Context(), tokenString)
	if err != nil {
		// Check if context was cancelled/timed out
		if ctxErr := c.Request.Context().Err(); ctxErr != nil {
			am.logger.Warn("Context cancelled during session validation",
				zap.Error(ctxErr),
				zap.String("user_id", userID.String()),
			)
			c.Abort()
			return uuid.Nil, false
		}

		am.logger.Warn("Invalid session",
			zap.Error(err),
			zap.String("user_id", userID.String()),
			zap.String("ip", c.ClientIP()),
		)
		response.Unauthorized(c, "Session expired or invalid")
		c.Abort()
		return uuid.Nil, false
	}

	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()),
		)
		response.Forbidden(c, "Session user mismatch")
		c.Abort()
		return uuid.Nil, false
	}

	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)

	// BE-SEC-008: Automatic session refresh if session is close to expiration
	// Refresh session if it expires within the next 25% of its lifetime
	sessionLifetime := session.ExpiresAt.Sub(session.CreatedAt)
	timeUntilExpiry := time.Until(session.ExpiresAt)
	refreshThreshold := sessionLifetime / 4 // Refresh when 25% of lifetime remains

	if timeUntilExpiry < refreshThreshold && timeUntilExpiry > 0 {
		// Calculate new expiration (extend by original lifetime)
		newExpiresIn := sessionLifetime
		if newExpiresIn == 0 {
			newExpiresIn = 30 * 24 * time.Hour // Default to 30 days
		}

		// Refresh the session asynchronously (non-blocking)
		go func() {
			ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
			defer cancel()

			if err := am.sessionService.RefreshSession(ctx, tokenString, newExpiresIn); err != nil {
				am.logger.Warn("Failed to auto-refresh session",
					zap.Error(err),
					zap.String("user_id", userID.String()),
					zap.String("session_id", session.ID.String()),
				)
			} else {
				am.logger.Debug("Session auto-refreshed",
					zap.String("user_id", userID.String()),
					zap.String("session_id", session.ID.String()),
					zap.Duration("new_expires_in", newExpiresIn),
				)
			}
		}()
	}

	// Log audit access
	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()),
		)
	}

	return userID, true
}

// RequireAuth middleware qui exige une authentification
func (am *AuthMiddleware) RequireAuth() gin.HandlerFunc {
	return func(c *gin.Context) {
		if _, ok := am.authenticate(c); ok {
			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]

		claims, err := am.jwtService.ValidateToken(tokenString)
		if err != nil {
			c.Next()
			return
		}
		userID := claims.UserID

		// T0204: Check TokenVersion (optional auth should also respect revocation)
		user, err := am.userService.GetByID(userID)
		if err != nil {
			c.Next()
			return
		}
		if err := am.jwtService.VerifyTokenVersion(claims, user.TokenVersion); err != nil {
			c.Next()
			return
		}

		session, err := am.sessionService.ValidateSession(c.Request.Context(), tokenString)
		if err != nil {
			// Check if context was cancelled/timed out
			if ctxErr := c.Request.Context().Err(); ctxErr != nil {
				c.Abort()
				return
			}
			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)

		// BE-SEC-008: Automatic session refresh if session is close to expiration
		// Refresh session if it expires within the next 25% of its lifetime
		sessionLifetime := session.ExpiresAt.Sub(session.CreatedAt)
		timeUntilExpiry := time.Until(session.ExpiresAt)
		refreshThreshold := sessionLifetime / 4 // Refresh when 25% of lifetime remains

		if timeUntilExpiry < refreshThreshold && timeUntilExpiry > 0 {
			// Calculate new expiration (extend by original lifetime)
			newExpiresIn := sessionLifetime
			if newExpiresIn == 0 {
				newExpiresIn = 30 * 24 * time.Hour // Default to 30 days
			}

			// Refresh the session asynchronously (non-blocking)
			go func() {
				ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
				defer cancel()

				if err := am.sessionService.RefreshSession(ctx, tokenString, newExpiresIn); err != nil {
					am.logger.Warn("Failed to auto-refresh session (optional auth)",
						zap.Error(err),
						zap.String("user_id", userID.String()),
						zap.String("session_id", session.ID.String()),
					)
				} else {
					am.logger.Debug("Session auto-refreshed (optional auth)",
						zap.String("user_id", userID.String()),
						zap.String("session_id", session.ID.String()),
						zap.Duration("new_expires_in", newExpiresIn),
					)
				}
			}()
		}

		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) {
		userID, ok := am.authenticate(c)
		if !ok {
			return
		}

		// 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))
			response.InternalServerError(c, "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()),
			)
			response.Forbidden(c, "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) {
		userID, ok := am.authenticate(c)
		if !ok {
			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))
			response.InternalServerError(c, "Internal server error")
			c.Abort()
			return
		}

		if !hasPermission {
			am.logger.Warn("Permission denied",
				zap.String("user_id", userID.String()),
				zap.String("permission", permission),
			)
			response.Forbidden(c, "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
// MVP: En développement, autoriser tous les utilisateurs authentifiés
func (am *AuthMiddleware) RequireContentCreatorRole() gin.HandlerFunc {
	return func(c *gin.Context) {
		userID, ok := am.authenticate(c)
		if !ok {
			return
		}

		// MVP: En développement, autoriser tous les utilisateurs authentifiés
		env := os.Getenv("ENVIRONMENT")
		if env == "" {
			env = os.Getenv("APP_ENV")
		}
		if env == "development" || env == "dev" {
			am.logger.Debug("MVP: Bypassing role check in development",
				zap.String("user_id", userID.String()),
				zap.String("environment", env),
			)
			c.Next()
			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),
			)
			response.Forbidden(c, "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()
	}
}

// ResourceOwnerResolver est une fonction qui récupère l'ID du propriétaire d'une ressource
// depuis le contexte de la requête (paramètres de route, etc.)
// Retourne l'UUID du propriétaire et une erreur si la ressource n'existe pas
type ResourceOwnerResolver func(c *gin.Context) (uuid.UUID, error)

// RequireOwnershipOrAdmin middleware qui vérifie que l'utilisateur authentifié est le propriétaire
// de la ressource ou qu'il a le rôle admin
// MOD-P0-003: Middleware générique pour vérification ownership centralisée
func (am *AuthMiddleware) RequireOwnershipOrAdmin(resourceType string, resolver ResourceOwnerResolver) gin.HandlerFunc {
	return func(c *gin.Context) {
		// Authentifier l'utilisateur
		userID, ok := am.authenticate(c)
		if !ok {
			return
		}

		// Récupérer l'ID du propriétaire de la ressource via le resolver
		resourceOwnerID, err := resolver(c)
		if err != nil {
			am.logger.Warn("Failed to resolve resource owner",
				zap.Error(err),
				zap.String("resource_type", resourceType),
				zap.String("user_id", userID.String()),
			)
			response.NotFound(c, "Resource not found")
			c.Abort()
			return
		}

		// Si l'utilisateur est le propriétaire, autoriser
		if userID == resourceOwnerID {
			c.Next()
			return
		}

		// Vérifier si l'utilisateur est admin
		hasRole, err := am.permissionService.HasRole(c.Request.Context(), userID, "admin")
		if err != nil {
			am.logger.Error("Failed to check admin role for ownership",
				zap.Error(err),
				zap.String("resource_type", resourceType),
				zap.String("user_id", userID.String()),
			)
			response.InternalServerError(c, "Internal server error")
			c.Abort()
			return
		}

		if hasRole {
			am.logger.Info("Admin override for ownership check",
				zap.String("resource_type", resourceType),
				zap.String("user_id", userID.String()),
				zap.String("resource_owner_id", resourceOwnerID.String()),
			)
			c.Next()
			return
		}

		// L'utilisateur n'est ni le propriétaire ni admin → Forbidden
		am.logger.Warn("Ownership check failed",
			zap.String("resource_type", resourceType),
			zap.String("user_id", userID.String()),
			zap.String("resource_owner_id", resourceOwnerID.String()),
			zap.String("ip", c.ClientIP()),
		)
		response.Forbidden(c, "You do not have permission to access this resource")
		c.Abort()
	}
}

// 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 == "" {
			response.Unauthorized(c, "Authorization header required")
			c.Abort()
			return
		}

		tokenParts := strings.Split(authHeader, " ")
		if len(tokenParts) != 2 || tokenParts[0] != "Bearer" {
			response.Unauthorized(c, "Invalid Authorization header format")
			c.Abort()
			return
		}

		tokenString := tokenParts[1]

		claims, err := am.jwtService.ValidateToken(tokenString)
		if err != nil {
			response.Unauthorized(c, "Invalid token")
			c.Abort()
			return
		}
		userID := claims.UserID

		// T0204: Check TokenVersion
		user, err := am.userService.GetByID(userID)
		if err != nil {
			response.Unauthorized(c, "User not found")
			c.Abort()
			return
		}
		if err := am.jwtService.VerifyTokenVersion(claims, user.TokenVersion); err != nil {
			response.Unauthorized(c, "Token revoked")
			c.Abort()
			return
		}

		session, err := am.sessionService.ValidateSession(c.Request.Context(), tokenString)
		if err != nil {
			// Check if context was cancelled/timed out
			if ctxErr := c.Request.Context().Err(); ctxErr != nil {
				c.Abort()
				return
			}

			response.Unauthorized(c, "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()),
			)
			response.InternalServerError(c, "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(),
		})
	}
}
