package auth

import (
	"context"
	"errors"
	"fmt"
	"os"
	"strings"
	"time"

	"veza-backend-api/internal/models"
	"veza-backend-api/internal/monitoring"
	"veza-backend-api/internal/services" // Added import for services

	"github.com/google/uuid"

	"go.uber.org/zap"
	"golang.org/x/crypto/bcrypt"
	"gorm.io/gorm"

	"veza-backend-api/internal/validators" // Import the validators package
)

type AuthService struct {
	db                       *gorm.DB
	logger                   *zap.Logger
	JWTService               services.JWTServiceInterface
	emailVerificationService services.EmailVerificationServiceInterface
	refreshTokenService      services.RefreshTokenServiceInterface
	passwordResetService     services.PasswordResetServiceInterface
	emailValidator           *validators.EmailValidator
	passwordValidator        *validators.PasswordValidator
	passwordService          services.PasswordServiceInterface
	emailService             services.EmailServiceInterface
	jobWorker                services.JobWorkerInterface
	accountLockoutService    *services.AccountLockoutService // Keeping concrete as per plan scope limit, or make interface if needed later
}

func NewAuthService(
	db *gorm.DB,
	emailValidator *validators.EmailValidator,
	passwordValidator *validators.PasswordValidator,
	passwordService services.PasswordServiceInterface,
	jwtService services.JWTServiceInterface,
	refreshTokenService services.RefreshTokenServiceInterface,
	emailVerificationService services.EmailVerificationServiceInterface,
	passwordResetService services.PasswordResetServiceInterface,
	emailService services.EmailServiceInterface,
	jobWorker services.JobWorkerInterface,
	logger *zap.Logger,
) *AuthService {
	return &AuthService{
		db:                       db,
		logger:                   logger,
		JWTService:               jwtService,
		emailVerificationService: emailVerificationService,
		refreshTokenService:      refreshTokenService,
		passwordResetService:     passwordResetService,
		emailValidator:           emailValidator,
		passwordValidator:        passwordValidator,
		passwordService:          passwordService,
		emailService:             emailService,
		jobWorker:                jobWorker,
		accountLockoutService:    nil, // Will be set via SetAccountLockoutService
	}
}

// SetAccountLockoutService définit le service de verrouillage de compte
// BE-SEC-007: Implement account lockout after failed login attempts
func (s *AuthService) SetAccountLockoutService(lockoutService *services.AccountLockoutService) {
	s.accountLockoutService = lockoutService
}

// GetUserByUsername récupère un utilisateur par son nom d'utilisateur
func (s *AuthService) GetUserByUsername(ctx context.Context, username string) (*models.User, error) {
	var user models.User
	if err := s.db.WithContext(ctx).Where("username = ?", username).First(&user).Error; err != nil {
		// FIX #10: Logger l'erreur avec contexte
		s.logger.Error("Failed to get user by username",
			zap.String("username", username),
			zap.Error(err),
		)
		return nil, err
	}
	return &user, nil
}

// Refresh est un alias pour RefreshToken
func (s *AuthService) Refresh(ctx context.Context, refreshToken string) (*models.TokenPair, error) {
	return s.RefreshToken(ctx, refreshToken)
}

func (s *AuthService) Register(ctx context.Context, email, username, password string) (*models.User, *models.TokenPair, error) {
	// FIX #5: Remplacer fmt.Print* par logs structurés
	s.logger.Debug("Registration started",
		zap.String("email", email),
		zap.String("username", username),
	)

	defer func() {
		if r := recover(); r != nil {
			s.logger.Error("PANIC in Register", zap.Any("panic", r))
			panic(r) // Re-panic pour que le middleware Recovery le capture
		}
	}()

	// Valider l'email
	s.logger.Debug("Validating email", zap.String("email", email))
	if err := s.emailValidator.Validate(email); err != nil {
		s.logger.Warn("Registration failed: invalid email", zap.String("email", email), zap.Error(err))
		// Utiliser le sentinel error pour que IsInvalidEmail() le détecte
		if strings.Contains(err.Error(), "already exists") {
			return nil, nil, services.ErrUserAlreadyExists
		}
		return nil, nil, fmt.Errorf("%w: %v", services.ErrInvalidEmail, err)
	}

	// Vérifier si le username existe déjà
	s.logger.Debug("Checking username uniqueness", zap.String("username", username))
	var usernameCount int64
	if err := s.db.WithContext(ctx).Model(&models.User{}).Where("LOWER(username) = LOWER(?)", username).Count(&usernameCount).Error; err != nil {
		s.logger.Error("Failed to check username uniqueness", zap.String("username", username), zap.Error(err))
		return nil, nil, fmt.Errorf("failed to check username: %w", err)
	}
	if usernameCount > 0 {
		s.logger.Warn("Registration failed: username already exists", zap.String("username", username))
		return nil, nil, services.ErrUserAlreadyExists
	}

	// Valider le mot de passe
	s.logger.Debug("Validating password", zap.String("email", email))
	passwordStrength, err := s.passwordValidator.Validate(password)
	if err != nil {
		s.logger.Warn("Registration failed: weak password", zap.String("email", email), zap.Error(err))
		return nil, nil, fmt.Errorf("%w: %v", services.ErrWeakPassword, err)
	}
	if !passwordStrength.Valid {
		s.logger.Warn("Registration failed: weak password", zap.String("email", email), zap.Any("details", passwordStrength.Details))
		details := strings.Join(passwordStrength.Details, ", ")
		return nil, nil, fmt.Errorf("%w: %s", services.ErrWeakPassword, details)
	}

	// Hacher le mot de passe
	s.logger.Debug("Hashing password", zap.String("email", email))
	hashedPassword, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)
	if err != nil {
		s.logger.Error("Failed to hash password", zap.Error(err))
		return nil, nil, err
	}

	// Générer un slug unique à partir du username
	// Le slug doit être unique, donc on vérifie et on ajoute un suffixe si nécessaire
	s.logger.Debug("Generating slug", zap.String("username", username))
	baseSlug := strings.ToLower(username)
	slug := baseSlug
	counter := 1
	for {
		var count int64
		err := s.db.WithContext(ctx).Model(&models.User{}).Where("slug = ?", slug).Count(&count).Error
		if err != nil {
			s.logger.Error("Failed to check slug uniqueness", zap.String("slug", slug), zap.Error(err))
			return nil, nil, err
		}
		if count == 0 {
			break
		}
		slug = fmt.Sprintf("%s%d", baseSlug, counter)
		counter++
		if counter > 1000 {
			// Fallback: utiliser un timestamp si trop de collisions
			slug = fmt.Sprintf("user_%d", time.Now().Unix())
			break
		}
	}
	s.logger.Debug("Slug generated", zap.String("slug", slug), zap.String("username", username))

	// Créer l'utilisateur dans la base de données
	// IMPORTANT: Initialiser explicitement tous les champs NOT NULL pour éviter les erreurs de contrainte
	s.logger.Debug("Creating user object", zap.String("email", email), zap.String("username", username))
	now := time.Now()
	user := &models.User{
		ID:           uuid.New(), // Générer un nouvel UUID
		Email:        email,
		Username:     username,
		Slug:         slug,
		PasswordHash: string(hashedPassword),
		Role:         "user", // Valeur par défaut (doit correspondre à l'ENUM PostgreSQL)
		IsActive:     true,   // Valeur par défaut
		IsVerified:   true,   // MVP: Auto-verify email pour permettre login immédiat
		IsBanned:     false,  // Valeur par défaut (required NOT NULL field)
		TokenVersion: 0,      // Valeur par défaut (required NOT NULL field)
		LoginCount:   0,      // Valeur par défaut (required NOT NULL field)
		CreatedAt:    now,    // Explicitement défini pour éviter les problèmes GORM
		UpdatedAt:    now,    // Explicitement défini pour éviter les problèmes GORM
	}
	s.logger.Debug("User object created",
		zap.String("user_id", user.ID.String()),
		zap.String("email", user.Email),
		zap.String("username", user.Username),
		zap.String("role", user.Role),
		zap.Bool("is_banned", user.IsBanned),
		zap.Int("login_count", user.LoginCount),
	)

	// Log les valeurs avant insertion pour diagnostic
	s.logger.Info("Creating user with values",
		zap.String("email", email),
		zap.String("username", username),
		zap.Bool("is_banned", user.IsBanned),
		zap.Int("login_count", user.LoginCount),
		zap.Int("token_version", user.TokenVersion),
		zap.Bool("is_active", user.IsActive),
		zap.Bool("is_verified", user.IsVerified),
		zap.String("role", user.Role),
		zap.String("slug", user.Slug),
		zap.String("user_id", user.ID.String()),
	)

	// Utiliser GORM Create - GORM gère automatiquement les placeholders PostgreSQL
	// Tous les champs NOT NULL sont maintenant explicitement initialisés
	// IMPORTANT: Utiliser Omit pour exclure les relations qui pourraient causer des problèmes
	s.logger.Debug("Inserting user in database",
		zap.String("user_id", user.ID.String()),
		zap.String("email", user.Email),
		zap.String("username", user.Username),
		zap.String("role", user.Role),
		zap.Bool("is_active", user.IsActive),
		zap.Bool("is_verified", user.IsVerified),
		zap.Bool("is_banned", user.IsBanned),
		zap.Int("token_version", user.TokenVersion),
		zap.Int("login_count", user.LoginCount),
	)

	result := s.db.WithContext(ctx).Omit("Roles", "TrackLikes").Create(user)
	if result.Error != nil {

		// Log l'erreur complète pour diagnostic
		err := result.Error
		errMsg := err.Error()
		errType := fmt.Sprintf("%T", err)

		s.logger.Error("Failed to create user in database - FULL ERROR DETAILS",
			zap.Error(err),
			zap.String("error_type", errType),
			zap.String("error_string", errMsg),
			zap.String("email", email),
			zap.String("username", username),
			zap.String("slug", slug),
			zap.String("role", user.Role),
			zap.String("user_id", user.ID.String()),
			zap.Any("user_fields", map[string]interface{}{
				"is_banned":     user.IsBanned,
				"login_count":   user.LoginCount,
				"token_version": user.TokenVersion,
				"is_active":     user.IsActive,
				"is_verified":   user.IsVerified,
				"role":          user.Role,
				"slug":          user.Slug,
			}),
		)

		// Vérifier les erreurs PostgreSQL spécifiques
		// Contrainte CHECK
		if strings.Contains(errMsg, "violates check constraint") {
			if strings.Contains(errMsg, "chk_users_username_format") {
				s.logger.Warn("Registration failed: username format invalid", zap.String("username", username))
				return nil, nil, errors.New("username format invalid: must be 3-30 alphanumeric characters")
			}
			if strings.Contains(errMsg, "chk_users_email_format") {
				s.logger.Warn("Registration failed: email format invalid", zap.String("email", email))
				return nil, nil, errors.New("email format invalid")
			}
			// Autre contrainte CHECK
			s.logger.Warn("Registration failed: check constraint violation", zap.Error(err))
			return nil, nil, fmt.Errorf("validation failed: %w", err)
		}

		// Type ENUM manquant ou valeur invalide
		if strings.Contains(errMsg, "does not exist") && strings.Contains(errMsg, "user_role") {
			s.logger.Error("Registration failed: user_role enum missing from database")
			return nil, nil, fmt.Errorf("database schema error: user_role enum missing - run migrations")
		}
		// Erreur de valeur ENUM invalide
		if strings.Contains(errMsg, "invalid input value for enum") || strings.Contains(errMsg, "invalid input syntax for type user_role") {
			s.logger.Error("Registration failed: invalid role value for enum",
				zap.String("role", user.Role),
				zap.Error(err))
			return nil, nil, fmt.Errorf("invalid role value '%s' for enum user_role: %w", user.Role, err)
		}

		// Timeout
		if strings.Contains(errMsg, "context deadline exceeded") || strings.Contains(errMsg, "timeout") {
			s.logger.Warn("Registration failed: database operation timed out")
			return nil, nil, fmt.Errorf("database operation timed out: %w", err)
		}

		// PostgreSQL error code 23505 is unique_violation
		// We check for specific constraint names if possible, or fallback to generic "duplicate"
		if strings.Contains(errMsg, "users_email_key") || strings.Contains(errMsg, "idx_users_email") {
			s.logger.Warn("Registration failed: email already exists", zap.String("email", email))
			return nil, nil, services.ErrUserAlreadyExists
		}
		if strings.Contains(errMsg, "users_username_key") || strings.Contains(errMsg, "idx_users_username") {
			s.logger.Warn("Registration failed: username already exists", zap.String("username", username))
			return nil, nil, services.ErrUserAlreadyExists
		}
		if strings.Contains(errMsg, "users_slug_key") || strings.Contains(errMsg, "idx_users_slug") {
			s.logger.Warn("Registration failed: slug collision", zap.String("slug", user.Slug))
			return nil, nil, errors.New("username unavailable (slug collision)")
		}

		// Fallback for generic unique constraint
		if strings.Contains(errMsg, "unique constraint") || strings.Contains(errMsg, "duplicate key") {
			s.logger.Warn("Registration failed: unique constraint violation", zap.Error(err))
			return nil, nil, services.ErrUserAlreadyExists
		}

		// Pour toutes les autres erreurs, retourner l'erreur originale avec contexte
		// IMPORTANT: Inclure l'erreur complète pour diagnostic
		s.logger.Error("Registration failed: unknown database error",
			zap.Error(err),
			zap.String("error_type", errType),
			zap.String("error_string", errMsg),
		)
		return nil, nil, fmt.Errorf("database error [%s]: %w", errType, err)
	}

	s.logger.Debug("User inserted successfully",
		zap.String("user_id", user.ID.String()),
		zap.Int64("rows_affected", result.RowsAffected),
	)

	// Générer le token de vérification d'email (non-bloquant)
	// Si la génération échoue, on continue quand même avec l'inscription
	// L'utilisateur pourra demander un nouveau token plus tard
	if s.emailVerificationService != nil {
		token, err := s.emailVerificationService.GenerateToken()
		if err != nil {
			s.logger.Warn("Failed to generate email verification token (non-blocking)", zap.Error(err))
		} else {
			// Stocker le token
			if err := s.emailVerificationService.StoreToken(user.ID, user.Email, token); err != nil {
				s.logger.Warn("Failed to store email verification token (non-blocking)", zap.Error(err))
			} else {
				// Envoyer l'email de vérification (simulation pour l'instant)
				s.logger.Info("Sending verification email",
					zap.String("email", user.Email),
					zap.String("token", token),
					zap.String("user_id", user.ID.String()))
			}
		}
	} else {
		s.logger.Warn("Email verification service not available - skipping token generation")
	}

	s.logger.Info("User registered successfully", zap.String("user_id", user.ID.String()))

	s.logger.Debug("Generating tokens", zap.String("user_id", user.ID.String()))

	// MVP: Générer les tokens JWT pour permettre l'authentification immédiate
	if s.JWTService == nil {
		s.logger.Error("JWTService is nil - cannot generate tokens")
		return nil, nil, fmt.Errorf("JWT service not available")
	}

	accessToken, err := s.JWTService.GenerateAccessToken(user)
	if err != nil {
		s.logger.Error("Failed to generate access token after registration", zap.Error(err), zap.String("user_id", user.ID.String()))
		return nil, nil, fmt.Errorf("failed to generate access token: %w", err)
	}
	s.logger.Debug("Access token generated", zap.String("user_id", user.ID.String()))

	refreshToken, err := s.JWTService.GenerateRefreshToken(user)
	if err != nil {
		s.logger.Error("Failed to generate refresh token after registration", zap.Error(err), zap.String("user_id", user.ID.String()))
		return nil, nil, fmt.Errorf("failed to generate refresh token: %w", err)
	}
	s.logger.Debug("Refresh token generated", zap.String("user_id", user.ID.String()))

	// Stocker le refresh token en base
	s.logger.Debug("Storing refresh token", zap.String("user_id", user.ID.String()))
	refreshTokenTTL := s.JWTService.GetConfig().RefreshTokenTTL
	if s.refreshTokenService != nil {
		if err := s.refreshTokenService.Store(user.ID, refreshToken, refreshTokenTTL); err != nil {
			s.logger.Error("Failed to store refresh token after registration", zap.Error(err), zap.String("user_id", user.ID.String()))
			return nil, nil, fmt.Errorf("failed to store refresh token: %w", err)
		}
		s.logger.Debug("Refresh token stored", zap.String("user_id", user.ID.String()))
	} else {
		s.logger.Warn("Refresh token service not available - skipping token storage", zap.String("user_id", user.ID.String()))
		s.logger.Warn("Refresh token service not available - skipping token storage")
	}

	// MOD-P2-003: Enregistrer la métrique business
	monitoring.RecordUserRegistered()

	tokenPair := &models.TokenPair{
		AccessToken:  accessToken,
		RefreshToken: refreshToken,
		ExpiresIn:    int(s.JWTService.GetConfig().AccessTokenTTL.Seconds()),
	}

	s.logger.Info("Registration completed successfully",
		zap.String("user_id", user.ID.String()),
		zap.String("email", email),
		zap.String("username", username),
	)

	return user, tokenPair, nil
}

func (s *AuthService) Login(ctx context.Context, email, password string, rememberMe bool) (*models.User, *models.TokenPair, error) {
	s.logger.Info("Attempting login", zap.String("email", email))

	// BE-SEC-007: Check if account is locked
	if s.accountLockoutService != nil {
		locked, lockedUntil, err := s.accountLockoutService.IsAccountLocked(ctx, email)
		if err != nil {
			s.logger.Warn("Failed to check account lockout status",
				zap.String("email", email),
				zap.Error(err))
			// Continue with login attempt if check fails (fail-open)
		} else if locked {
			if lockedUntil != nil {
				remaining := time.Until(*lockedUntil)
				s.logger.Warn("Login blocked: account is locked",
					zap.String("email", email),
					zap.Time("locked_until", *lockedUntil),
					zap.Duration("remaining", remaining))
				return nil, nil, fmt.Errorf("account is locked. Please try again after %v", remaining.Round(time.Minute))
			}
			return nil, nil, errors.New("account is locked due to too many failed login attempts")
		}
	}

	var user models.User
	if err := s.db.WithContext(ctx).Where("email = ?", email).First(&user).Error; err != nil {
		if err == gorm.ErrRecordNotFound {
			s.logger.Warn("Login failed: user not found", zap.String("email", email))
			// BE-SEC-007: Record failed attempt even if user not found (to prevent email enumeration)
			if s.accountLockoutService != nil {
				if err := s.accountLockoutService.RecordFailedAttempt(ctx, email); err != nil {
					s.logger.Warn("Failed to record failed login attempt",
						zap.String("email", email),
						zap.Error(err))
				}
			}
			return nil, nil, errors.New("invalid credentials")
		}
		s.logger.Error("Database error during login", zap.Error(err), zap.String("email", email))
		return nil, nil, err
	}

	if err := bcrypt.CompareHashAndPassword([]byte(user.PasswordHash), []byte(password)); err != nil {
		s.logger.Warn("Login failed: invalid password", zap.String("email", email))
		// BE-SEC-007: Record failed attempt
		if s.accountLockoutService != nil {
			if err := s.accountLockoutService.RecordFailedAttempt(ctx, email); err != nil {
				s.logger.Warn("Failed to record failed login attempt",
					zap.String("email", email),
					zap.Error(err))
			}
		}
		return nil, nil, errors.New("invalid credentials")
	}

	if !user.IsVerified {
		s.logger.Warn("Login failed: email not verified", zap.String("email", email))
		// BE-SEC-007: Record failed attempt (email not verified is also a failed attempt)
		if s.accountLockoutService != nil {
			if err := s.accountLockoutService.RecordFailedAttempt(ctx, email); err != nil {
				s.logger.Warn("Failed to record failed login attempt",
					zap.String("email", email),
					zap.Error(err))
			}
		}
		return nil, nil, errors.New("email not verified")
	}

	// BE-SEC-007: Record successful login (reset failed attempts counter)
	if s.accountLockoutService != nil {
		if err := s.accountLockoutService.RecordSuccessfulLogin(ctx, email); err != nil {
			s.logger.Warn("Failed to record successful login",
				zap.String("email", email),
				zap.Error(err))
			// Non-critique, on continue
		}
	}

	// Générer les tokens JWT
	accessToken, err := s.JWTService.GenerateAccessToken(&user)
	if err != nil {
		s.logger.Error("Failed to generate access token", zap.Error(err), zap.String("user_id", user.ID.String()))
		return nil, nil, fmt.Errorf("failed to generate access token: %w", err)
	}

	refreshTokenTTL := s.JWTService.GetConfig().RefreshTokenTTL
	if rememberMe {
		refreshTokenTTL = s.JWTService.GetConfig().RememberMeRefreshTokenTTL // Assurez-vous que ce champ existe dans models.JWTConfig
	}
	refreshToken, err := s.JWTService.GenerateRefreshToken(&user)
	if err != nil {
		s.logger.Error("Failed to generate refresh token", zap.Error(err), zap.String("user_id", user.ID.String()))
		return nil, nil, fmt.Errorf("failed to generate refresh token: %w", err)
	}

	// Stocker le refresh token en base
	if err := s.refreshTokenService.Store(user.ID, refreshToken, refreshTokenTTL); err != nil {
		s.logger.Error("Failed to store refresh token", zap.Error(err), zap.String("user_id", user.ID.String()))
		return nil, nil, fmt.Errorf("failed to store refresh token: %w", err)
	}

	s.logger.Info("User logged in successfully", zap.String("user_id", user.ID.String()))

	return &user, &models.TokenPair{
		AccessToken:  accessToken,
		RefreshToken: refreshToken,
		ExpiresIn:    int(s.JWTService.GetConfig().AccessTokenTTL.Seconds()),
	}, nil
}

func (s *AuthService) RefreshToken(ctx context.Context, refreshToken string) (*models.TokenPair, error) {
	claims, err := s.JWTService.ValidateToken(refreshToken)
	if err != nil {
		s.logger.Warn("Invalid refresh token format", zap.Error(err))
		return nil, errors.New("invalid refresh token")
	}

	if !claims.IsRefresh {
		s.logger.Warn("Token is not a refresh token")
		return nil, errors.New("invalid token type")
	}

	if err := s.refreshTokenService.Validate(claims.UserID, refreshToken); err != nil {
		s.logger.Warn("Refresh token invalid or revoked", zap.Error(err))
		return nil, errors.New("invalid or revoked refresh token")
	}

	var user models.User
	if err := s.db.WithContext(ctx).First(&user, claims.UserID).Error; err != nil {
		s.logger.Error("User not found for refresh token", zap.Error(err))
		return nil, errors.New("user not found")
	}

	newAccessToken, err := s.JWTService.GenerateAccessToken(&user)
	if err != nil {
		s.logger.Error("Failed to generate new access token", zap.Error(err))
		return nil, err
	}

	newRefreshToken, err := s.JWTService.GenerateRefreshToken(&user)
	if err != nil {
		s.logger.Error("Failed to generate new refresh token", zap.Error(err))
		return nil, err
	}

	if err := s.refreshTokenService.Rotate(user.ID, refreshToken, newRefreshToken, s.JWTService.GetConfig().RefreshTokenTTL); err != nil {
		s.logger.Error("Failed to rotate refresh token", zap.Error(err))
		return nil, err
	}

	return &models.TokenPair{
		AccessToken:  newAccessToken,
		RefreshToken: newRefreshToken,
		ExpiresIn:    int(s.JWTService.GetConfig().AccessTokenTTL.Seconds()),
	}, nil
}

func (s *AuthService) VerifyEmail(ctx context.Context, token string) error {
	userID, err := s.emailVerificationService.VerifyToken(token)
	if err != nil {
		s.logger.Warn("Email verification failed", zap.Error(err))
		return err
	}

	if err := s.db.WithContext(ctx).Model(&models.User{}).Where("id = ?", userID).Update("is_verified", true).Error; err != nil {
		s.logger.Error("Failed to update user verification status", zap.Error(err))
		return err
	}

	if err := s.emailVerificationService.InvalidateOldTokens(userID); err != nil {
		s.logger.Warn("Failed to invalidate old verification tokens", zap.Error(err))
	}

	s.logger.Info("Email verified successfully", zap.String("user_id", userID.String()))
	return nil
}

func (s *AuthService) ResendVerificationEmail(ctx context.Context, email string) error {
	var user models.User
	if err := s.db.WithContext(ctx).Where("email = ?", email).First(&user).Error; err != nil {
		if err == gorm.ErrRecordNotFound {
			return nil
		}
		return err
	}

	if user.IsVerified {
		return errors.New("email already verified")
	}

	if err := s.emailVerificationService.InvalidateOldTokens(user.ID); err != nil {
		s.logger.Error("Failed to invalidate old tokens", zap.Error(err))
	}

	token, err := s.emailVerificationService.GenerateToken()
	if err != nil {
		return err
	}

	if err := s.emailVerificationService.StoreToken(user.ID, user.Email, token); err != nil {
		return err
	}

	s.logger.Info("Resending verification email",
		zap.String("email", user.Email),
		zap.String("token", token),
		zap.String("user_id", user.ID.String()))

	return nil
}

func (s *AuthService) Logout(ctx context.Context, userID uuid.UUID, refreshToken string) error {
	// Valider le refresh token
	claims, err := s.JWTService.ValidateToken(refreshToken)
	if err != nil {
		s.logger.Warn("Invalid refresh token during logout", zap.Error(err), zap.String("user_id", userID.String()))
		return nil // Ne pas retourner d'erreur pour ne pas bloquer le logout côté UI
	}

	if claims.UserID != userID {
		s.logger.Warn("User ID mismatch for logout request", zap.String("requested_user_id", userID.String()), zap.String("token_user_id", claims.UserID.String()))
		return errors.New("user ID mismatch")
	}

	if err := s.refreshTokenService.Revoke(claims.UserID, refreshToken); err != nil {
		s.logger.Error("Failed to revoke refresh token during logout", zap.Error(err), zap.String("user_id", userID.String()))
		return err
	}

	s.logger.Info("User logged out successfully", zap.String("user_id", userID.String()))
	return nil
}

func (s *AuthService) InvalidateAllUserSessions(ctx context.Context, userID uuid.UUID, sessionService interface {
	RevokeAllUserSessions(ctx context.Context, userID uuid.UUID) (int64, error)
}) error {
	if err := s.refreshTokenService.RevokeAll(userID); err != nil {
		s.logger.Error("Failed to revoke all refresh tokens", zap.Error(err))
		return err
	}

	if sessionService != nil {
		count, err := sessionService.RevokeAllUserSessions(ctx, userID)
		if err != nil {
			s.logger.Error("Failed to revoke user sessions", zap.Error(err))
		} else {
			s.logger.Info("Revoked user sessions", zap.Int64("count", count), zap.String("user_id", userID.String()))
		}
	}

	s.logger.Info("All user sessions invalidated", zap.String("user_id", userID.String()))
	return nil
}

// MIGRATION UUID: userID migré vers uuid.UUID
func (s *AuthService) AdminVerifyUser(ctx context.Context, userID uuid.UUID) error {
	result := s.db.WithContext(ctx).Model(&models.User{}).Where("id = ?", userID).Update("is_verified", true)
	if result.Error != nil {
		return result.Error
	}
	if result.RowsAffected == 0 {
		return errors.New("user not found")
	}

	_ = s.emailVerificationService.InvalidateOldTokens(userID)

	s.logger.Info("User verified by admin", zap.String("user_id", userID.String()))
	return nil
}

// MIGRATION UUID: userID migré vers uuid.UUID
func (s *AuthService) AdminBlockUser(ctx context.Context, userID uuid.UUID) error {
	if err := s.refreshTokenService.RevokeAll(userID); err != nil {
		return err
	}

	s.logger.Info("User blocked by admin", zap.String("user_id", userID.String()))
	return nil
}

func (s *AuthService) RequestPasswordReset(ctx context.Context, email string) error {
	var user models.User
	if err := s.db.WithContext(ctx).Where("email = ?", email).First(&user).Error; err != nil {
		if err == gorm.ErrRecordNotFound {
			// Return nil to prevent email enumeration - always return success
			return nil
		}
		return err
	}

	// Invalidate old tokens for this user
	if err := s.passwordResetService.InvalidateOldTokens(user.ID); err != nil {
		s.logger.Warn("Failed to invalidate old password reset tokens",
			zap.String("user_id", user.ID.String()),
			zap.Error(err),
		)
		// Continue anyway, not critical
	}

	// Generate new reset token
	token, err := s.passwordResetService.GenerateToken()
	if err != nil {
		s.logger.Error("Failed to generate password reset token",
			zap.String("user_id", user.ID.String()),
			zap.Error(err),
		)
		return fmt.Errorf("failed to generate reset token: %w", err)
	}

	// Store token in database
	if err := s.passwordResetService.StoreToken(user.ID, token); err != nil {
		s.logger.Error("Failed to store password reset token",
			zap.String("user_id", user.ID.String()),
			zap.Error(err),
		)
		return fmt.Errorf("failed to store reset token: %w", err)
	}

	// Send password reset email via job worker (asynchrone)
	if s.jobWorker != nil {
		// Construire l'URL de reset
		baseURL := os.Getenv("FRONTEND_URL")
		if baseURL == "" {
			baseURL = "http://localhost:5173"
		}
		resetURL := fmt.Sprintf("%s/reset-password?token=%s", baseURL, token)

		// Préparer les données du template
		templateData := map[string]interface{}{
			"Username": user.Username,
			"ResetURL": resetURL,
		}

		// Enqueue le job d'email avec template
		s.jobWorker.EnqueueEmailJobWithTemplate(
			user.Email,
			"Reset your Veza password",
			"password_reset",
			templateData,
		)

		s.logger.Info("Password reset email job enqueued",
			zap.String("user_id", user.ID.String()),
			zap.String("email", user.Email),
		)
	} else {
		// Fallback sur l'ancien système si job worker non disponible
		s.logger.Warn("Job worker not available, using direct email service")
		if err := s.emailService.SendPasswordResetEmail(user.ID, user.Email, token); err != nil {
			s.logger.Error("Failed to send password reset email",
				zap.String("user_id", user.ID.String()),
				zap.String("email", user.Email),
				zap.Error(err),
			)
		}
	}

	s.logger.Info("Password reset requested successfully",
		zap.String("email", email),
		zap.String("user_id", user.ID.String()),
		zap.String("token_preview", token[:min(len(token), 8)]+"..."),
	)
	return nil
}

func (s *AuthService) ResetPassword(ctx context.Context, token, newPassword string) error {
	// Verify the reset token
	userID, err := s.passwordResetService.VerifyToken(token)
	if err != nil {
		s.logger.Warn("Password reset token verification failed",
			zap.String("token_preview", token[:min(len(token), 8)]+"..."),
			zap.Error(err),
		)
		return fmt.Errorf("invalid or expired token: %w", err)
	}

	// Validate password strength
	if err := s.passwordService.ValidatePassword(newPassword); err != nil {
		s.logger.Warn("Password validation failed during reset",
			zap.String("user_id", userID.String()),
			zap.Error(err),
		)
		return fmt.Errorf("invalid password: %w", err)
	}

	// Update password using PasswordService
	if err := s.passwordService.UpdatePassword(userID, newPassword); err != nil {
		s.logger.Error("Failed to update password during reset",
			zap.String("user_id", userID.String()),
			zap.Error(err),
		)
		return fmt.Errorf("failed to update password: %w", err)
	}

	// Mark token as used
	if err := s.passwordResetService.MarkTokenAsUsed(token); err != nil {
		// Log but don't fail - password is already updated
		s.logger.Warn("Failed to mark password reset token as used",
			zap.String("user_id", userID.String()),
			zap.String("token_preview", token[:min(len(token), 8)]+"..."),
			zap.Error(err),
		)
	}

	// Invalidate all user sessions (revoke refresh tokens)
	if err := s.refreshTokenService.RevokeAll(userID); err != nil {
		s.logger.Warn("Failed to revoke refresh tokens after password reset",
			zap.String("user_id", userID.String()),
			zap.Error(err),
		)
		// Don't fail - password is already updated
	}

	s.logger.Info("Password reset completed successfully",
		zap.String("user_id", userID.String()),
	)
	return nil
}

// MIGRATION UUID: userID migré vers uuid.UUID
func (s *AuthService) ChangePassword(ctx context.Context, userID uuid.UUID, currentPassword, newPassword string) error {
	var user models.User
	if err := s.db.WithContext(ctx).First(&user, userID).Error; err != nil {
		return err
	}

	if err := bcrypt.CompareHashAndPassword([]byte(user.PasswordHash), []byte(currentPassword)); err != nil {
		return errors.New("invalid current password")
	}

	hashedPassword, err := bcrypt.GenerateFromPassword([]byte(newPassword), bcrypt.DefaultCost)
	if err != nil {
		return err
	}

	if err := s.db.WithContext(ctx).Model(&user).Update("password_hash", string(hashedPassword)).Error; err != nil {
		return err
	}

	if err := s.refreshTokenService.RevokeAll(userID); err != nil {
		s.logger.Warn("Failed to revoke refresh tokens after password change", zap.Error(err))
	}

	s.logger.Info("Password changed successfully", zap.String("user_id", userID.String()))
	return nil
}

func (s *AuthService) ValidateAccessToken(tokenString string) (*models.CustomClaims, error) {
	return s.JWTService.ValidateToken(tokenString)
}

func (s *AuthService) UpdateLastLogin(ctx context.Context, userID uuid.UUID) error {
	return s.db.WithContext(ctx).Model(&models.User{}).
		Where("id = ?", userID).
		Update("last_login_at", time.Now()).Error
}

// min returns the minimum of two integers (helper function)
func min(a, b int) int {
	if a < b {
		return a
	}
	return b
}
