package services

import (
	"context"
	"fmt"
	"time"

	"github.com/redis/go-redis/v9"
	"go.uber.org/zap"
)

// AccountLockoutService gère le verrouillage de compte après tentatives échouées
// BE-SEC-007: Implement account lockout after failed login attempts
type AccountLockoutService struct {
	redisClient     *redis.Client
	logger          *zap.Logger
	maxAttempts     int           // Nombre maximum de tentatives avant verrouillage (défaut: 5)
	lockoutDuration time.Duration // Durée du verrouillage (défaut: 30 minutes)
	windowDuration  time.Duration // Fenêtre de temps pour compter les tentatives (défaut: 15 minutes)
}

// AccountLockoutConfig configuration pour le service de verrouillage
type AccountLockoutConfig struct {
	MaxAttempts     int           // Nombre maximum de tentatives (défaut: 5)
	LockoutDuration time.Duration // Durée du verrouillage (défaut: 30 minutes)
	WindowDuration  time.Duration // Fenêtre de temps pour compter les tentatives (défaut: 15 minutes)
}

// DefaultAccountLockoutConfig retourne la configuration par défaut
func DefaultAccountLockoutConfig() *AccountLockoutConfig {
	return &AccountLockoutConfig{
		MaxAttempts:     5,
		LockoutDuration: 30 * time.Minute,
		WindowDuration:  15 * time.Minute,
	}
}

// NewAccountLockoutService crée un nouveau service de verrouillage de compte
func NewAccountLockoutService(redisClient *redis.Client, logger *zap.Logger) *AccountLockoutService {
	config := DefaultAccountLockoutConfig()
	return &AccountLockoutService{
		redisClient:     redisClient,
		logger:          logger,
		maxAttempts:     config.MaxAttempts,
		lockoutDuration: config.LockoutDuration,
		windowDuration:  config.WindowDuration,
	}
}

// NewAccountLockoutServiceWithConfig crée un nouveau service avec configuration personnalisée
func NewAccountLockoutServiceWithConfig(redisClient *redis.Client, logger *zap.Logger, config *AccountLockoutConfig) *AccountLockoutService {
	if config == nil {
		config = DefaultAccountLockoutConfig()
	}
	return &AccountLockoutService{
		redisClient:     redisClient,
		logger:          logger,
		maxAttempts:     config.MaxAttempts,
		lockoutDuration: config.LockoutDuration,
		windowDuration:  config.WindowDuration,
	}
}

// RecordFailedAttempt enregistre une tentative de login échouée
func (s *AccountLockoutService) RecordFailedAttempt(ctx context.Context, email string) error {
	if s.redisClient == nil {
		// Si Redis n'est pas disponible, on ne peut pas tracker les tentatives
		// On retourne nil pour ne pas bloquer le login
		s.logger.Warn("Redis not available - account lockout disabled")
		return nil
	}

	key := s.getAttemptsKey(email)

	// Incrémenter le compteur de tentatives
	count, err := s.redisClient.Incr(ctx, key).Result()
	if err != nil {
		return fmt.Errorf("failed to record failed attempt: %w", err)
	}

	// Définir l'expiration de la clé (fenêtre de temps)
	if count == 1 {
		// Première tentative, définir l'expiration
		if err := s.redisClient.Expire(ctx, key, s.windowDuration).Err(); err != nil {
			s.logger.Warn("Failed to set expiration for attempts key", zap.Error(err))
		}
	}

	// Si on atteint le maximum de tentatives, verrouiller le compte
	if count >= int64(s.maxAttempts) {
		if err := s.LockAccount(ctx, email); err != nil {
			s.logger.Error("Failed to lock account after max attempts",
				zap.String("email", email),
				zap.Int64("attempts", count),
				zap.Error(err))
			return err
		}
		s.logger.Warn("Account locked due to too many failed login attempts",
			zap.String("email", email),
			zap.Int64("attempts", count))
	}

	return nil
}

// RecordSuccessfulLogin réinitialise le compteur de tentatives échouées après un login réussi
func (s *AccountLockoutService) RecordSuccessfulLogin(ctx context.Context, email string) error {
	if s.redisClient == nil {
		return nil
	}

	// Supprimer le compteur de tentatives échouées
	key := s.getAttemptsKey(email)
	if err := s.redisClient.Del(ctx, key).Err(); err != nil {
		s.logger.Warn("Failed to reset failed attempts counter",
			zap.String("email", email),
			zap.Error(err))
		return err
	}

	// Déverrouiller le compte si il était verrouillé
	if err := s.UnlockAccount(ctx, email); err != nil {
		s.logger.Warn("Failed to unlock account after successful login",
			zap.String("email", email),
			zap.Error(err))
		// Ne pas retourner d'erreur, c'est non-critique
	}

	return nil
}

// IsAccountLocked vérifie si un compte est verrouillé
func (s *AccountLockoutService) IsAccountLocked(ctx context.Context, email string) (bool, *time.Time, error) {
	if s.redisClient == nil {
		return false, nil, nil
	}

	key := s.getLockoutKey(email)
	lockedUntilStr, err := s.redisClient.Get(ctx, key).Result()
	if err == redis.Nil {
		// Pas de verrouillage
		return false, nil, nil
	}
	if err != nil {
		return false, nil, fmt.Errorf("failed to check account lockout: %w", err)
	}

	// Parser la date de déverrouillage
	lockedUntil, err := time.Parse(time.RFC3339, lockedUntilStr)
	if err != nil {
		s.logger.Warn("Failed to parse lockout expiration",
			zap.String("email", email),
			zap.String("locked_until", lockedUntilStr),
			zap.Error(err))
		// Si on ne peut pas parser, considérer comme non verrouillé
		return false, nil, nil
	}

	// Vérifier si le verrouillage a expiré
	if time.Now().After(lockedUntil) {
		// Le verrouillage a expiré, le supprimer
		if err := s.UnlockAccount(ctx, email); err != nil {
			s.logger.Warn("Failed to unlock expired account",
				zap.String("email", email),
				zap.Error(err))
		}
		return false, nil, nil
	}

	return true, &lockedUntil, nil
}

// LockAccount verrouille un compte
func (s *AccountLockoutService) LockAccount(ctx context.Context, email string) error {
	if s.redisClient == nil {
		return nil
	}

	key := s.getLockoutKey(email)
	lockedUntil := time.Now().Add(s.lockoutDuration)
	lockedUntilStr := lockedUntil.Format(time.RFC3339)

	if err := s.redisClient.Set(ctx, key, lockedUntilStr, s.lockoutDuration).Err(); err != nil {
		return fmt.Errorf("failed to lock account: %w", err)
	}

	return nil
}

// UnlockAccount déverrouille un compte
func (s *AccountLockoutService) UnlockAccount(ctx context.Context, email string) error {
	if s.redisClient == nil {
		return nil
	}

	key := s.getLockoutKey(email)
	if err := s.redisClient.Del(ctx, key).Err(); err != nil {
		return fmt.Errorf("failed to unlock account: %w", err)
	}

	// Aussi supprimer le compteur de tentatives
	attemptsKey := s.getAttemptsKey(email)
	if err := s.redisClient.Del(ctx, attemptsKey).Err(); err != nil {
		s.logger.Warn("Failed to delete attempts counter",
			zap.String("email", email),
			zap.Error(err))
		// Non-critique
	}

	return nil
}

// GetFailedAttemptsCount retourne le nombre de tentatives échouées pour un email
func (s *AccountLockoutService) GetFailedAttemptsCount(ctx context.Context, email string) (int, error) {
	if s.redisClient == nil {
		return 0, nil
	}

	key := s.getAttemptsKey(email)
	count, err := s.redisClient.Get(ctx, key).Int()
	if err == redis.Nil {
		return 0, nil
	}
	if err != nil {
		return 0, fmt.Errorf("failed to get failed attempts count: %w", err)
	}

	return count, nil
}

// getAttemptsKey génère la clé Redis pour le compteur de tentatives
func (s *AccountLockoutService) getAttemptsKey(email string) string {
	return fmt.Sprintf("account_lockout:attempts:%s", email)
}

// getLockoutKey génère la clé Redis pour le statut de verrouillage
func (s *AccountLockoutService) getLockoutKey(email string) string {
	return fmt.Sprintf("account_lockout:locked:%s", email)
}
