package services

import (
	"github.com/google/uuid"
	"context"
	"fmt"
	"strings"
	"time"

	"gorm.io/gorm"
	"veza-backend-api/internal/models"
	"veza-backend-api/internal/utils"
	"veza-backend-api/internal/validators"
	"go.uber.org/zap"
)

// TokenPair représente une paire de tokens d'authentification
type TokenPair struct {
	AccessToken  string `json:"access_token"`
	RefreshToken string `json:"refresh_token"`
}

// AuthService gère l'authentification des utilisateurs pour T0155
type AuthService struct {
	db                       *gorm.DB
	emailValidator           *validators.EmailValidator
	passwordValidator        *validators.PasswordValidator
	passwordService          *PasswordService
	jwtService               *JWTService
	refreshTokenService      *RefreshTokenService // T0165: Service pour gérer les refresh tokens
	emailVerificationService *EmailVerificationService // T0184: Service pour générer et stocker les tokens de vérification
	emailService             *EmailService             // T0184: Service pour envoyer les emails
	logger                   *zap.Logger               // T0184: Logger pour les erreurs d'envoi d'email
}

// NewAuthService crée une nouvelle instance d'AuthService avec toutes les dépendances
// T0165: Ajoute RefreshTokenService pour le stockage des refresh tokens
// T0184: Ajoute EmailVerificationService et EmailService pour l'envoi d'emails de vérification
func NewAuthService(
	db *gorm.DB,
	emailValidator *validators.EmailValidator,
	passwordValidator *validators.PasswordValidator,
	passwordService *PasswordService,
	jwtService *JWTService,
	refreshTokenService *RefreshTokenService,
	emailVerificationService *EmailVerificationService,
	emailService *EmailService,
	logger *zap.Logger,
) *AuthService {
	return &AuthService{
		db:                       db,
		emailValidator:           emailValidator,
		passwordValidator:        passwordValidator,
		passwordService:          passwordService,
		jwtService:               jwtService,
		refreshTokenService:      refreshTokenService,
		emailVerificationService: emailVerificationService,
		emailService:             emailService,
		logger:                   logger,
	}
}

// Register enregistre un nouvel utilisateur avec email et password
// T0155: Utilise EmailValidator, PasswordValidator, PasswordService et JWTService
// T0156: Accepte un username optionnel du frontend, sinon génère depuis l'email
func (s *AuthService) Register(email, password string, providedUsername ...string) (*models.User, *TokenPair, error) {
	// Normaliser l'email
	email = strings.ToLower(strings.TrimSpace(email))

	// Validate email (format + unicité)
	if err := s.emailValidator.Validate(email); err != nil {
		return nil, nil, err
	}

	// Validate password strength
	strength, err := s.passwordValidator.Validate(password)
	if err != nil {
		return nil, nil, fmt.Errorf("password validation error: %w", err)
	}
	if !strength.Valid {
		return nil, nil, fmt.Errorf("password does not meet requirements: %s", strings.Join(strength.Details, ", "))
	}

	// Hash password
	hashedPassword, err := s.passwordService.Hash(password)
	if err != nil {
		return nil, nil, fmt.Errorf("failed to hash password: %w", err)
	}

	// Déterminer le username : utiliser celui fourni par le frontend s'il existe, sinon générer depuis l'email
	var username string

	if len(providedUsername) > 0 && providedUsername[0] != "" {
		// Utiliser le username fourni par le frontend
		username = strings.TrimSpace(providedUsername[0])
		// Vérifier que le username n'existe pas déjà
		var count int64
		s.db.Model(&models.User{}).Where("username = ?", username).Count(&count)
		if count > 0 {
			return nil, nil, fmt.Errorf("username already exists")
		}
	} else {
		// Générer un username depuis l'email (partie avant @)
		baseUsername := strings.Split(email, "@")[0]
		username, err = s.ensureUnique(baseUsername, "username")
		if err != nil {
			return nil, nil, fmt.Errorf("failed to generate username: %w", err)
		}
	}

	// T0219: Generate slug from username
	baseSlug := utils.Slugify(username)
	slug, err := s.ensureUnique(baseSlug, "slug")
	if err != nil {
		return nil, nil, fmt.Errorf("failed to generate slug: %w", err)
	}

	// Create user
	user := &models.User{
		Email:        email,
		Username:     username,
		Slug:         slug,
		PasswordHash: hashedPassword,
		Role:         "user",
		IsActive:     true,
		IsVerified:   false,
		TokenVersion: 0,
	}

	if err := s.db.Create(user).Error; err != nil {
		return nil, nil, fmt.Errorf("failed to create user: %w", err)
	}

	// Generate tokens
	accessToken, err := s.jwtService.GenerateAccessToken(user)
	if err != nil {
		return nil, nil, fmt.Errorf("failed to generate access token: %w", err)
	}

	refreshToken, err := s.jwtService.GenerateRefreshToken(user)
	if err != nil {
		return nil, nil, fmt.Errorf("failed to generate refresh token: %w", err)
	}

	tokens := &TokenPair{
		AccessToken:  accessToken,
		RefreshToken: refreshToken,
	}

		// T0184: Étape 1 - Générer token de vérification après création user
	// MIGRATION UUID: user.ID est maintenant uuid.UUID
	if s.emailVerificationService != nil && s.emailService != nil {
		// Generate verification token
		token, err := s.emailVerificationService.GenerateToken()
		if err != nil {
			// Log l'erreur mais ne pas faire échouer l'inscription
			if s.logger != nil {
				s.logger.Warn("Failed to generate verification token", zap.Error(err), zap.String("user_id", user.ID.String()))
			}
		} else {
			// Store token
			if err := s.emailVerificationService.StoreToken(user.ID, token); err != nil {
				// Log l'erreur mais ne pas faire échouer l'inscription
				if s.logger != nil {
					s.logger.Warn("Failed to store verification token", zap.Error(err), zap.String("user_id", user.ID.String()))
				}
			} else {
				// Send verification email
				if err := s.emailService.SendVerificationEmail(user.Email, token); err != nil {
					// Log l'erreur mais ne pas faire échouer l'inscription
					if s.logger != nil {
						s.logger.Warn("Failed to send verification email", zap.Error(err), zap.String("user_id", user.ID.String()))
					}
					// Don't fail registration if email fails
				}
			}
		}
	}

	return user, tokens, nil
}

// Login authentifie un utilisateur avec email et password
// T0161: Valide credentials, génère JWT et refresh token, met à jour last_login_at
// T0165: Intègre RefreshTokenService pour stocker le refresh token en base
func (s *AuthService) Login(email, password string, rememberMe bool) (*models.User, *TokenPair, error) {
	// Normaliser l'email
	email = strings.ToLower(strings.TrimSpace(email))

	// Récupérer l'utilisateur par email
	var user models.User
	if err := s.db.Where("email = ? AND is_active = ?", email, true).First(&user).Error; err != nil {
		if err == gorm.ErrRecordNotFound {
			return nil, nil, fmt.Errorf("invalid credentials")
		}
		return nil, nil, fmt.Errorf("failed to find user: %w", err)
	}

	// Vérifier le mot de passe
	if !s.passwordService.Compare(user.PasswordHash, password) {
		return nil, nil, fmt.Errorf("invalid credentials")
	}

	// T0188: Vérifier que l'email est vérifié
	if !user.IsVerified {
		return nil, nil, fmt.Errorf("email not verified: please check your inbox for verification link")
	}

	// Mettre à jour last_login_at
	now := time.Now()
	user.LastLoginAt = &now
	if err := s.db.Model(&user).Update("last_login_at", now).Error; err != nil {
		// Log l'erreur mais ne pas bloquer la connexion
		// On continue quand même car la mise à jour de last_login_at n'est pas critique
	}

	// T0165: Générer les tokens avec GenerateTokenPair
	tokens, err := s.jwtService.GenerateTokenPair(&user)
	if err != nil {
		return nil, nil, fmt.Errorf("failed to generate tokens: %w", err)
	}

	// T0165: Calculer la date d'expiration du refresh token (30 jours par défaut)
	refreshTokenExpiry := time.Now().Add(30 * 24 * time.Hour)
	if rememberMe {
		// Si rememberMe est activé, étendre à 90 jours
		refreshTokenExpiry = time.Now().Add(90 * 24 * time.Hour)
	}

	// T0165: Stocker le refresh token en base
	if s.refreshTokenService != nil {
		if err := s.refreshTokenService.Store(user.ID, tokens.RefreshToken, refreshTokenExpiry); err != nil {
			return nil, nil, fmt.Errorf("failed to store refresh token: %w", err)
		}
	}

	// Log l'émission du token (succès de connexion)
	// MIGRATION UUID: Utilise String() pour logger l'UUID
	s.logger.Info("auth_token_issued",
		zap.String("user_id", user.ID.String()),
		zap.String("username", user.Username),
		zap.String("email", user.Email),
		zap.Time("expires_at", refreshTokenExpiry),
	)

	return &user, tokens, nil
}

// Refresh génère un nouveau access token à partir d'un refresh token valide
// T0172: Valide le refresh token, génère un nouveau access token
func (s *AuthService) Refresh(refreshToken string) (*TokenPair, error) {
	// T0172: Valider le refresh token JWT
	claims, err := s.jwtService.ValidateToken(refreshToken)
	if err != nil {
		return nil, fmt.Errorf("invalid refresh token: %w", err)
	}

	// T0172: Vérifier que le refresh token est bien stocké en base et valide
	// (vérification de sécurité supplémentaire)
	if s.refreshTokenService != nil {
		valid, err := s.refreshTokenService.Validate(claims.UserID, refreshToken)
		if err != nil {
			return nil, fmt.Errorf("failed to validate refresh token: %w", err)
		}
		if !valid {
			return nil, fmt.Errorf("refresh token not found or expired")
		}
	}

	// T0172: Récupérer l'utilisateur depuis la base de données
	var user models.User
	if err := s.db.Where("id = ? AND is_active = ?", claims.UserID, true).First(&user).Error; err != nil {
		if err == gorm.ErrRecordNotFound {
			return nil, fmt.Errorf("user not found")
		}
		return nil, fmt.Errorf("failed to find user: %w", err)
	}

	// T0172: Vérifier que la version du token correspond à celle de l'utilisateur
	// (pour invalider les tokens après changement de mot de passe, etc.)
	if err := s.jwtService.VerifyTokenVersion(claims, user.TokenVersion); err != nil {
		return nil, fmt.Errorf("token version mismatch: %w", err)
	}

	// T0172: Générer un nouveau access token
	accessToken, err := s.jwtService.GenerateAccessToken(&user)
	if err != nil {
		return nil, fmt.Errorf("failed to generate access token: %w", err)
	}

	// T0172: Retourner le nouveau access token (on garde le même refresh token)
	return &TokenPair{
		AccessToken:  accessToken,
		RefreshToken: refreshToken, // Le refresh token reste le même
	}, nil
}

// InvalidateAllUserSessions invalide toutes les sessions d'un utilisateur
// T0200: Met à jour token_version dans la DB et révoque toutes les sessions
// MIGRATION UUID: userID est maintenant uuid.UUID
func (s *AuthService) InvalidateAllUserSessions(userID uuid.UUID, sessionService interface {
	RevokeAllUserSessionsByUserID(ctx context.Context, userID uuid.UUID) (int64, error)
}) error {
	// T0200: Mettre à jour token_version pour invalider tous les tokens existants
	result := s.db.Model(&models.User{}).
		Where("id = ?", userID).
		Update("token_version", gorm.Expr("token_version + 1"))

	if result.Error != nil {
		s.logger.Error("Failed to increment token version",
			zap.String("user_id", userID.String()),
			zap.Error(result.Error),
		)
		return fmt.Errorf("failed to invalidate user sessions: %w", result.Error)
	}

	if result.RowsAffected == 0 {
		s.logger.Warn("User not found when invalidating sessions",
			zap.String("user_id", userID.String()),
		)
		return fmt.Errorf("user not found")
	}

	// T0200: Révoquer toutes les sessions actives de l'utilisateur
	if sessionService != nil {
		ctx := context.Background()
		revokedCount, err := sessionService.RevokeAllUserSessionsByUserID(ctx, userID)
		if err != nil {
			// Log but don't fail - token_version update already invalidates tokens
			s.logger.Warn("Failed to revoke user sessions",
				zap.String("user_id", userID.String()),
				zap.Error(err),
			)
		} else {
			s.logger.Info("User sessions invalidated",
				zap.String("user_id", userID.String()),
				zap.Int64("sessions_revoked", revokedCount),
			)
		}
	}

	s.logger.Info("All user sessions invalidated via token version update",
		zap.String("user_id", userID.String()),
	)

	return nil
}

// GetUserByUsername récupère un utilisateur par son nom d'utilisateur
func (s *AuthService) GetUserByUsername(username string) (*models.User, error) {
	var user models.User
	if err := s.db.Where("username = ?", username).First(&user).Error; err != nil {
		if err == gorm.ErrRecordNotFound {
			return nil, fmt.Errorf("user with username '%s' not found", username)
		}
		return nil, fmt.Errorf("failed to find user: %w", err)
	}
	return &user, nil
}

// VerifyEmail vérifie le token et met à jour le statut de l'utilisateur
// T0183: Vérifie le token via EmailVerificationService et met à jour is_verified
func (s *AuthService) VerifyEmail(token string) error {
	userID, err := s.emailVerificationService.VerifyToken(token)
	if err != nil {
		return err
	}

	// Mettre à jour le statut de l'utilisateur
	if err := s.db.Model(&models.User{}).Where("id = ?", userID).Updates(map[string]interface{}{
		"is_verified":       true,
		// "email_verified_at": time.Now(), // Si la colonne existe
	}).Error; err != nil {
		return fmt.Errorf("failed to update user verification status: %w", err)
	}

	return nil
}

// ResendVerificationEmail renvoie l'email de vérification
// T0186: Génère un nouveau token et renvoie l'email
func (s *AuthService) ResendVerificationEmail(email string) error {
	// 1. Récupérer l'utilisateur
	var user models.User
	if err := s.db.Where("email = ?", email).First(&user).Error; err != nil {
		// Pour sécurité, ne pas dire si l'email n'existe pas
		return nil 
	}

	if user.IsVerified {
		return fmt.Errorf("email already verified")
	}

	// 2. Invalider les anciens tokens
	s.emailVerificationService.InvalidateOldTokens(user.ID)

	// 3. Générer nouveau token
	token, err := s.emailVerificationService.GenerateToken()
	if err != nil {
		return fmt.Errorf("failed to generate token: %w", err)
	}

	// 4. Stocker le token
	if err := s.emailVerificationService.StoreToken(user.ID, token); err != nil {
		return fmt.Errorf("failed to store token: %w", err)
	}

	// 5. Envoyer l'email
	return s.emailService.SendVerificationEmail(email, token)
}

// Logout déconnecte l'utilisateur en révoquant le refresh token
// MIGRATION UUID: userID est maintenant uuid.UUID
func (s *AuthService) Logout(userID uuid.UUID, refreshToken string) error {
	if s.refreshTokenService == nil {
		return nil // Service non disponible (ne devrait pas arriver en prod)
	}
	
	// Révoquer le token spécifique
	return s.refreshTokenService.Revoke(userID, refreshToken)
}

// ensureUnique ensures a value is unique in the database for a given field
func (s *AuthService) ensureUnique(baseValue, field string) (string, error) {
	value := baseValue
	counter := 1
	for {
		var count int64
		// Note: fmt.Sprintf is safe here because field is internal constant string, not user input
		if err := s.db.Model(&models.User{}).Where(fmt.Sprintf("%s = ?", field), value).Count(&count).Error; err != nil {
			return "", err
		}
		if count == 0 {
			return value, nil
		}

		value = fmt.Sprintf("%s%d", baseValue, counter)
		counter++
		if counter > 1000 {
			// Fallback to timestamp if too many collisions
			return fmt.Sprintf("user_%d", uuid.New()), nil
		}
	}
}
