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"
)

// setupTestPasswordResetCleanupDB crée une base de données de test avec la table password_reset_tokens
func setupTestPasswordResetCleanupDB(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 password_reset_tokens manuellement
	err = gormDB.Exec(`
		CREATE TABLE password_reset_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 password_reset_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
}

// TestCleanupExpiredPasswordResetTokens_ExpiredTokens supprime les tokens expirés
func TestCleanupExpiredPasswordResetTokens_ExpiredTokens(t *testing.T) {
	testDB, gormDB := setupTestPasswordResetCleanupDB(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 password_reset_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 password_reset_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 password_reset_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 = CleanupExpiredPasswordResetTokens(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 password_reset_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 password_reset_tokens WHERE token = 'valid_token'").Scan(&count).Error
	require.NoError(t, err)
	assert.Equal(t, int64(1), count, "Valid token should still exist")
}

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

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

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

	// Exécuter le nettoyage
	err = CleanupExpiredPasswordResetTokens(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 password_reset_tokens WHERE token = 'used_token_old'").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 password_reset_tokens WHERE token = 'used_token_recent'").Scan(&count).Error
	require.NoError(t, err)
	assert.Equal(t, int64(1), count, "Recent used token should still exist")
}

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

	// Créer un token expiré
	expiredTime := time.Now().Add(-25 * time.Hour)
	err := gormDB.Exec(`
		INSERT INTO password_reset_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
	eightDaysAgo := time.Now().Add(-8 * 24 * time.Hour)
	err = gormDB.Exec(`
		INSERT INTO password_reset_tokens (user_id, token, expires_at, used, created_at)
		VALUES (?, ?, ?, ?, ?)
	`, 1, "used_token_old", time.Now().Add(24*time.Hour), true, eightDaysAgo).Error
	require.NoError(t, err)

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

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

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

	// Vérifier que seuls les tokens valides restent
	var countAfter int64
	err = gormDB.Raw("SELECT COUNT(*) FROM password_reset_tokens").Scan(&countAfter).Error
	require.NoError(t, err)
	assert.Equal(t, int64(1), countAfter, "Only valid token should remain")

	// Vérifier que c'est bien le token valide qui reste
	var countValid int64
	err = gormDB.Raw("SELECT COUNT(*) FROM password_reset_tokens WHERE token = 'valid_token'").Scan(&countValid).Error
	require.NoError(t, err)
	assert.Equal(t, int64(1), countValid, "Valid token should still exist")
}

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

	// Créer uniquement des tokens valides
	validTime := time.Now().Add(24 * time.Hour)
	err := gormDB.Exec(`
		INSERT INTO password_reset_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 password_reset_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 password_reset_tokens").Scan(&countBefore).Error
	require.NoError(t, err)
	assert.Equal(t, int64(2), countBefore)

	// Exécuter le nettoyage
	err = CleanupExpiredPasswordResetTokens(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 password_reset_tokens").Scan(&countAfter).Error
	require.NoError(t, err)
	assert.Equal(t, countBefore, countAfter, "All valid tokens should still exist")
}
