package services

import (
	"crypto/sha256"
	"encoding/hex"
	"errors"
	"time"

	"veza-backend-api/internal/models"

	"github.com/google/uuid"
	"gorm.io/gorm"
)

// RefreshTokenService gère le stockage et la validation des refresh tokens
// T0164: Service pour gérer les refresh tokens avec stockage en base et validation
type RefreshTokenService struct {
	db *gorm.DB
}

// NewRefreshTokenService crée une nouvelle instance de RefreshTokenService
func NewRefreshTokenService(db *gorm.DB) *RefreshTokenService {
	return &RefreshTokenService{db: db}
}

// Store stocke un refresh token en base de données (hashé pour la sécurité)
// T0164: Stocke le token hashé avec userID et expiration
// MIGRATION UUID: userID migré vers uuid.UUID
func (s *RefreshTokenService) Store(userID uuid.UUID, token string, ttl time.Duration) error {
	tokenHash := s.hashToken(token)
	// Ajouter un buffer de 1 seconde pour garantir que expires_at > created_at
	// Cela évite les problèmes de précision des timestamps entre Go et PostgreSQL
	expiresAt := time.Now().Add(ttl).Add(1 * time.Second)

	refreshToken := &models.RefreshToken{
		UserID:    userID,
		TokenHash: tokenHash,
		ExpiresAt: expiresAt,
	}

	return s.db.Create(refreshToken).Error
}

// Validate vérifie si un refresh token est valide
// T0164: Valide le token en vérifiant son hash et sa date d'expiration
// MIGRATION UUID: userID migré vers uuid.UUID
func (s *RefreshTokenService) Validate(userID uuid.UUID, token string) error {
	tokenHash := s.hashToken(token)

	var refreshToken models.RefreshToken
	err := s.db.Where("user_id = ? AND token_hash = ?", userID, tokenHash).
		First(&refreshToken).Error

	if err != nil {
		if errors.Is(err, gorm.ErrRecordNotFound) {
			return errors.New("refresh token not found")
		}
		return err
	}

	// Vérifier si le token n'a pas expiré
	if time.Now().After(refreshToken.ExpiresAt) {
		return errors.New("refresh token expired")
	}

	return nil
}

// Rotate invalide l'ancien refresh token et en stocke un nouveau
// MIGRATION UUID: userID migré vers uuid.UUID
func (s *RefreshTokenService) Rotate(userID uuid.UUID, oldToken, newToken string, ttl time.Duration) error {
	// Transaction pour assurer l'atomicité
	return s.db.Transaction(func(tx *gorm.DB) error {
		// Révoquer l'ancien
		oldTokenHash := s.hashToken(oldToken)
		if err := tx.Where("user_id = ? AND token_hash = ?", userID, oldTokenHash).Delete(&models.RefreshToken{}).Error; err != nil {
			return err
		}

		// Stocker le nouveau
		newTokenHash := s.hashToken(newToken)
		// Ajouter un buffer de 1 seconde pour garantir que expires_at > created_at
		// Cela évite les problèmes de précision des timestamps entre Go et PostgreSQL
		refreshToken := &models.RefreshToken{
			UserID:    userID,
			TokenHash: newTokenHash,
			ExpiresAt: time.Now().Add(ttl).Add(1 * time.Second),
		}

		return tx.Create(refreshToken).Error
	})
}

// Revoke supprime/révoque un refresh token
// T0164: Supprime le token de la base de données
// MIGRATION UUID: userID migré vers uuid.UUID
func (s *RefreshTokenService) Revoke(userID uuid.UUID, token string) error {
	tokenHash := s.hashToken(token)

	result := s.db.Where("user_id = ? AND token_hash = ?", userID, tokenHash).
		Delete(&models.RefreshToken{})

	if result.Error != nil {
		return result.Error
	}

	if result.RowsAffected == 0 {
		// Ce n'est pas forcément une erreur critique si le token n'existait déjà plus
		return nil
	}

	return nil
}

// RevokeAll révoque tous les refresh tokens d'un utilisateur
// Utile pour la déconnexion de tous les appareils
// MIGRATION UUID: userID migré vers uuid.UUID
func (s *RefreshTokenService) RevokeAll(userID uuid.UUID) error {
	result := s.db.Where("user_id = ?", userID).
		Delete(&models.RefreshToken{})

	return result.Error
}

// hashToken hash un token avec SHA-256 pour le stockage sécurisé
func (s *RefreshTokenService) hashToken(token string) string {
	hash := sha256.Sum256([]byte(token))
	return hex.EncodeToString(hash[:])
}

// HashToken expose la méthode hashToken pour les tests
// T0171: Méthode publique pour hasher les tokens (utilisée dans les tests)
func (s *RefreshTokenService) HashToken(token string) string {
	return s.hashToken(token)
}
