package jobs

import (
	"testing"
	"time"

	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"
	"go.uber.org/zap"
	"gorm.io/driver/sqlite"
	"gorm.io/gorm"
	"veza-backend-api/internal/database"
	"veza-backend-api/internal/models"
)

// setupTestCleanupDB crée une base de données de test avec la table email_verification_tokens
func setupTestCleanupDB(t *testing.T) (*database.Database, *gorm.DB) {
	// Créer une base de données GORM en mémoire
	gormDB, err := gorm.Open(sqlite.Open(":memory:"), &gorm.Config{})
	require.NoError(t, err, "Failed to open test database")

	// Auto-migrate pour créer la table users
	err = gormDB.AutoMigrate(&models.User{})
	require.NoError(t, err, "Failed to migrate users table")

	// Créer la table email_verification_tokens manuellement
	err = gormDB.Exec(`
		CREATE TABLE email_verification_tokens (
			id INTEGER PRIMARY KEY AUTOINCREMENT,
			user_id INTEGER NOT NULL REFERENCES users(id) ON DELETE CASCADE,
			token TEXT NOT NULL UNIQUE,
			expires_at TIMESTAMP NOT NULL,
			used INTEGER NOT NULL DEFAULT 0,
			created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
		)
	`).Error
	require.NoError(t, err, "Failed to create email_verification_tokens table")

	// Créer un utilisateur de test
	user := &models.User{
		Email:    "test@example.com",
		Username: "testuser",
		Role:     "user",
		IsActive: true,
	}
	err = gormDB.Create(user).Error
	require.NoError(t, err, "Failed to create test user")

	// Obtenir le sql.DB depuis GORM
	sqlDB, err := gormDB.DB()
	require.NoError(t, err, "Failed to get sql.DB from GORM")

	// Créer un Database wrapper
	testDB := &database.Database{
		DB: sqlDB,
	}

	return testDB, gormDB
}

// TestCleanupExpiredVerificationTokens_ExpiredTokens supprime les tokens expirés
func TestCleanupExpiredVerificationTokens_ExpiredTokens(t *testing.T) {
	testDB, gormDB := setupTestCleanupDB(t)
	logger, _ := zap.NewDevelopment()

	// Créer des tokens expirés
	expiredTime := time.Now().Add(-25 * time.Hour) // Expiré il y a 25 heures
	err := gormDB.Exec(`
		INSERT INTO email_verification_tokens (user_id, token, expires_at, used, created_at)
		VALUES (?, ?, ?, ?, ?)
	`, 1, "expired_token_1", expiredTime, false, time.Now().Add(-26*time.Hour)).Error
	require.NoError(t, err)

	err = gormDB.Exec(`
		INSERT INTO email_verification_tokens (user_id, token, expires_at, used, created_at)
		VALUES (?, ?, ?, ?, ?)
	`, 1, "expired_token_2", expiredTime, false, time.Now().Add(-26*time.Hour)).Error
	require.NoError(t, err)

	// Créer un token valide (non expiré)
	validTime := time.Now().Add(24 * time.Hour)
	err = gormDB.Exec(`
		INSERT INTO email_verification_tokens (user_id, token, expires_at, used, created_at)
		VALUES (?, ?, ?, ?, ?)
	`, 1, "valid_token", validTime, false, time.Now()).Error
	require.NoError(t, err)

	// Exécuter le nettoyage
	err = CleanupExpiredVerificationTokens(testDB, logger)
	assert.NoError(t, err)

	// Vérifier que les tokens expirés ont été supprimés
	var count int64
	err = gormDB.Raw("SELECT COUNT(*) FROM email_verification_tokens WHERE token IN ('expired_token_1', 'expired_token_2')").Scan(&count).Error
	require.NoError(t, err)
	assert.Equal(t, int64(0), count, "Expired tokens should be deleted")

	// Vérifier que le token valide est toujours présent
	err = gormDB.Raw("SELECT COUNT(*) FROM email_verification_tokens WHERE token = 'valid_token'").Scan(&count).Error
	require.NoError(t, err)
	assert.Equal(t, int64(1), count, "Valid token should still exist")
}

// TestCleanupExpiredVerificationTokens_UsedTokensOlderThan7Days supprime les tokens utilisés plus anciens que 7 jours
func TestCleanupExpiredVerificationTokens_UsedTokensOlderThan7Days(t *testing.T) {
	testDB, gormDB := setupTestCleanupDB(t)
	logger, _ := zap.NewDevelopment()

	// Créer un token utilisé il y a 8 jours (doit être supprimé)
	oldUsedTime := time.Now().Add(-8 * 24 * time.Hour)
	err := gormDB.Exec(`
		INSERT INTO email_verification_tokens (user_id, token, expires_at, used, created_at)
		VALUES (?, ?, ?, ?, ?)
	`, 1, "old_used_token", time.Now().Add(24*time.Hour), true, oldUsedTime).Error
	require.NoError(t, err)

	// Créer un token utilisé il y a 5 jours (ne doit pas être supprimé)
	recentUsedTime := time.Now().Add(-5 * 24 * time.Hour)
	err = gormDB.Exec(`
		INSERT INTO email_verification_tokens (user_id, token, expires_at, used, created_at)
		VALUES (?, ?, ?, ?, ?)
	`, 1, "recent_used_token", time.Now().Add(24*time.Hour), true, recentUsedTime).Error
	require.NoError(t, err)

	// Exécuter le nettoyage
	err = CleanupExpiredVerificationTokens(testDB, logger)
	assert.NoError(t, err)

	// Vérifier que le token utilisé ancien a été supprimé
	var count int64
	err = gormDB.Raw("SELECT COUNT(*) FROM email_verification_tokens WHERE token = 'old_used_token'").Scan(&count).Error
	require.NoError(t, err)
	assert.Equal(t, int64(0), count, "Old used token should be deleted")

	// Vérifier que le token utilisé récent est toujours présent
	err = gormDB.Raw("SELECT COUNT(*) FROM email_verification_tokens WHERE token = 'recent_used_token'").Scan(&count).Error
	require.NoError(t, err)
	assert.Equal(t, int64(1), count, "Recent used token should still exist")
}

// TestCleanupExpiredVerificationTokens_MixedTokens supprime les tokens expirés et les tokens utilisés anciens
func TestCleanupExpiredVerificationTokens_MixedTokens(t *testing.T) {
	testDB, gormDB := setupTestCleanupDB(t)
	logger, _ := zap.NewDevelopment()

	// Créer un token expiré
	expiredTime := time.Now().Add(-25 * time.Hour)
	err := gormDB.Exec(`
		INSERT INTO email_verification_tokens (user_id, token, expires_at, used, created_at)
		VALUES (?, ?, ?, ?, ?)
	`, 1, "expired_token", expiredTime, false, time.Now().Add(-26*time.Hour)).Error
	require.NoError(t, err)

	// Créer un token utilisé ancien
	oldUsedTime := time.Now().Add(-8 * 24 * time.Hour)
	err = gormDB.Exec(`
		INSERT INTO email_verification_tokens (user_id, token, expires_at, used, created_at)
		VALUES (?, ?, ?, ?, ?)
	`, 1, "old_used_token", time.Now().Add(24*time.Hour), true, oldUsedTime).Error
	require.NoError(t, err)

	// Créer un token valide et non utilisé
	validTime := time.Now().Add(24 * time.Hour)
	err = gormDB.Exec(`
		INSERT INTO email_verification_tokens (user_id, token, expires_at, used, created_at)
		VALUES (?, ?, ?, ?, ?)
	`, 1, "valid_token", validTime, false, time.Now()).Error
	require.NoError(t, err)

	// Exécuter le nettoyage
	err = CleanupExpiredVerificationTokens(testDB, logger)
	assert.NoError(t, err)

	// Vérifier que les tokens expirés et anciens utilisés ont été supprimés
	var count int64
	err = gormDB.Raw("SELECT COUNT(*) FROM email_verification_tokens WHERE token IN ('expired_token', 'old_used_token')").Scan(&count).Error
	require.NoError(t, err)
	assert.Equal(t, int64(0), count, "Expired and old used tokens should be deleted")

	// Vérifier que le token valide est toujours présent
	err = gormDB.Raw("SELECT COUNT(*) FROM email_verification_tokens WHERE token = 'valid_token'").Scan(&count).Error
	require.NoError(t, err)
	assert.Equal(t, int64(1), count, "Valid token should still exist")
}

// TestCleanupExpiredVerificationTokens_NoTokensToClean ne fait rien s'il n'y a pas de tokens à nettoyer
func TestCleanupExpiredVerificationTokens_NoTokensToClean(t *testing.T) {
	testDB, gormDB := setupTestCleanupDB(t)
	logger, _ := zap.NewDevelopment()

	// Créer uniquement des tokens valides
	validTime := time.Now().Add(24 * time.Hour)
	err := gormDB.Exec(`
		INSERT INTO email_verification_tokens (user_id, token, expires_at, used, created_at)
		VALUES (?, ?, ?, ?, ?)
	`, 1, "valid_token_1", validTime, false, time.Now()).Error
	require.NoError(t, err)

	err = gormDB.Exec(`
		INSERT INTO email_verification_tokens (user_id, token, expires_at, used, created_at)
		VALUES (?, ?, ?, ?, ?)
	`, 1, "valid_token_2", validTime, false, time.Now()).Error
	require.NoError(t, err)

	// Compter les tokens avant le nettoyage
	var countBefore int64
	err = gormDB.Raw("SELECT COUNT(*) FROM email_verification_tokens").Scan(&countBefore).Error
	require.NoError(t, err)
	assert.Equal(t, int64(2), countBefore)

	// Exécuter le nettoyage
	err = CleanupExpiredVerificationTokens(testDB, logger)
	assert.NoError(t, err)

	// Vérifier que tous les tokens sont toujours présents
	var countAfter int64
	err = gormDB.Raw("SELECT COUNT(*) FROM email_verification_tokens").Scan(&countAfter).Error
	require.NoError(t, err)
	assert.Equal(t, countBefore, countAfter, "All valid tokens should still exist")
}

// TestScheduleCleanupJob programme le job correctement
func TestScheduleCleanupJob(t *testing.T) {
	testDB, _ := setupTestCleanupDB(t)
	logger, _ := zap.NewDevelopment()

	// Programmer le job
	ScheduleVerificationTokenCleanupJob(testDB, logger)

	// Le job devrait être programmé sans erreur
	// On ne peut pas tester facilement l'exécution périodique sans attendre 24h,
	// mais on peut vérifier qu'il n'y a pas d'erreur de compilation/initialisation

	// Attendre un peu pour s'assurer que le job initial s'exécute
	time.Sleep(100 * time.Millisecond)
}
