package services

import (
	"context"
	"crypto/rand"
	"database/sql"
	"encoding/base64"
	"fmt"
	"time"

	"github.com/google/uuid"
	"veza-backend-api/internal/database"

	"go.uber.org/zap"
)

// EmailVerificationService gère la génération, le stockage et la validation des tokens de vérification email
// T0182: Service pour gérer les tokens de vérification email avec expiration et invalidation
type EmailVerificationService struct {
	db     *database.Database
	logger *zap.Logger
}

// NewEmailVerificationService crée une nouvelle instance d'EmailVerificationService
func NewEmailVerificationService(db *database.Database, logger *zap.Logger) *EmailVerificationService {
	return &EmailVerificationService{
		db:     db,
		logger: logger,
	}
}

// GenerateToken génère un token aléatoire sécurisé de 32 bytes encodé en base64 URL-safe
// T0182: Génère un token aléatoire pour la vérification d'email
func (s *EmailVerificationService) GenerateToken() (string, error) {
	bytes := make([]byte, 32)
	if _, err := rand.Read(bytes); err != nil {
		s.logger.Error("Failed to generate random token", zap.Error(err))
		return "", fmt.Errorf("failed to generate token: %w", err)
	}
	return base64.URLEncoding.EncodeToString(bytes), nil
}

// StoreToken stocke un token de vérification en base de données avec une expiration de 24h
// T0182: Sauvegarde le token avec expiration 24h
// MIGRATION UUID: userID est maintenant int64
// MIGRATION UUID: userID migré vers uuid.UUID
func (s *EmailVerificationService) StoreToken(userID uuid.UUID, token string) error {
	ctx := context.Background()
	expiresAt := time.Now().Add(24 * time.Hour)

	_, err := s.db.ExecContext(ctx,
		"INSERT INTO email_verification_tokens (user_id, token, expires_at, used) VALUES ($1, $2, $3, FALSE)",
		userID, token, expiresAt,
	)
	if err != nil {
		s.logger.Error("Failed to store verification token",
			zap.String("user_id", userID.String()),
			zap.Error(err),
		)
		return fmt.Errorf("failed to store token: %w", err)
	}

	s.logger.Info("Verification token stored",
		zap.String("user_id", userID.String()),
		zap.Time("expires_at", expiresAt),
	)

	return nil
}

// VerifyToken valide un token de vérification, vérifie son expiration et le marque comme utilisé
// T0182: Valide le token, vérifie l'expiration et marque comme utilisé
// MIGRATION UUID: retourne uuid.UUID au lieu de int64
func (s *EmailVerificationService) VerifyToken(token string) (uuid.UUID, error) {
	ctx := context.Background()
	var userID uuid.UUID
	var expiresAt time.Time
	var used bool

	err := s.db.QueryRowContext(ctx,
		"SELECT user_id, expires_at, used FROM email_verification_tokens WHERE token = $1",
		token,
	).Scan(&userID, &expiresAt, &used)

	if err == sql.ErrNoRows {
		tokenPreview := token
		if len(token) > 8 {
			tokenPreview = token[:8] + "..."
		}
		s.logger.Warn("Verification token not found", zap.String("token", tokenPreview))
		return uuid.Nil, fmt.Errorf("invalid token")
	}
	if err != nil {
		s.logger.Error("Failed to verify token", zap.Error(err))
		return uuid.Nil, fmt.Errorf("failed to verify token: %w", err)
	}

	if used {
		tokenPreview := token
		if len(token) > 8 {
			tokenPreview = token[:8] + "..."
		}
		s.logger.Warn("Verification token already used",
			zap.String("user_id", userID.String()),
			zap.String("token", tokenPreview),
		)
		return uuid.Nil, fmt.Errorf("token already used")
	}

	if time.Now().After(expiresAt) {
		s.logger.Warn("Verification token expired",
			zap.String("user_id", userID.String()),
			zap.Time("expires_at", expiresAt),
		)
		return uuid.Nil, fmt.Errorf("token expired")
	}

	// Mark as used
	_, err = s.db.ExecContext(ctx, "UPDATE email_verification_tokens SET used = TRUE WHERE token = $1", token)
	if err != nil {
		s.logger.Error("Failed to mark token as used",
			zap.String("user_id", userID.String()),
			zap.Error(err),
		)
		return uuid.Nil, fmt.Errorf("failed to mark token as used: %w", err)
	}

	s.logger.Info("Verification token verified successfully",
		zap.String("user_id", userID.String()),
	)

	return userID, nil
}

// InvalidateOldTokens invalide tous les tokens de vérification précédents pour un utilisateur
// T0182: Invalide les tokens précédents pour un utilisateur
// MIGRATION UUID: userID migré vers uuid.UUID
func (s *EmailVerificationService) InvalidateOldTokens(userID uuid.UUID) error {
	ctx := context.Background()

	result, err := s.db.ExecContext(ctx,
		"UPDATE email_verification_tokens SET used = TRUE WHERE user_id = $1 AND used = FALSE",
		userID,
	)
	if err != nil {
		s.logger.Error("Failed to invalidate old tokens",
			zap.String("user_id", userID.String()),
			zap.Error(err),
		)
		return fmt.Errorf("failed to invalidate old tokens: %w", err)
	}

	rowsAffected, err := result.RowsAffected()
	if err != nil {
		s.logger.Warn("Failed to get rows affected", zap.Error(err))
	} else {
		s.logger.Info("Old verification tokens invalidated",
			zap.String("user_id", userID.String()),
			zap.Int64("tokens_invalidated", rowsAffected),
		)
	}

	return nil
}
