package jobs

import (
	"context"
	"testing"
	"time"

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

// MockSessionServiceForCleanup pour les tests
type MockSessionServiceForCleanup struct {
	mock.Mock
}

func (m *MockSessionServiceForCleanup) CleanupExpiredSessions(ctx context.Context) (int64, error) {
	args := m.Called(ctx)
	return args.Get(0).(int64), args.Error(1)
}

// TestCleanupExpiredSessions_Success teste le nettoyage réussi des sessions expirées
func TestCleanupExpiredSessions_Success(t *testing.T) {
	// Créer une base de données de test
	gormDB, err := gorm.Open(sqlite.Open(":memory:"), &gorm.Config{})
	require.NoError(t, err)

	// Créer la table sessions
	err = gormDB.Exec(`
		CREATE TABLE sessions (
			id INTEGER PRIMARY KEY AUTOINCREMENT,
			user_id INTEGER NOT NULL,
			token_hash TEXT NOT NULL,
			ip_address TEXT,
			user_agent TEXT,
			expires_at TIMESTAMP NOT NULL,
			revoked_at TIMESTAMP,
			last_activity TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
			created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
		)
	`).Error
	require.NoError(t, err)

	// Insérer des sessions expirées et non expirées
	now := time.Now()
	expiredTime := now.Add(-1 * time.Hour)
	futureTime := now.Add(24 * time.Hour)

	err = gormDB.Exec(`
		INSERT INTO sessions (user_id, token_hash, expires_at, created_at)
		VALUES 
			(1, 'hash1', ?, ?),
			(1, 'hash2', ?, ?),
			(2, 'hash3', ?, ?)
	`, expiredTime, now, expiredTime, now, futureTime, now).Error
	require.NoError(t, err)

	sqlDB, err := gormDB.DB()
	require.NoError(t, err)

	testDB := &database.Database{
		DB: sqlDB,
	}

	logger := zap.NewNop()

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

	// Vérifier que les sessions expirées ont été supprimées
	var count int64
	err = gormDB.Raw("SELECT COUNT(*) FROM sessions").Scan(&count).Error
	require.NoError(t, err)
	assert.Equal(t, int64(1), count, "Only one non-expired session should remain")
}

// TestCleanupExpiredSessions_NoExpiredSessions teste le cas où il n'y a pas de sessions expirées
func TestCleanupExpiredSessions_NoExpiredSessions(t *testing.T) {
	// Créer une base de données de test
	gormDB, err := gorm.Open(sqlite.Open(":memory:"), &gorm.Config{})
	require.NoError(t, err)

	// Créer la table sessions
	err = gormDB.Exec(`
		CREATE TABLE sessions (
			id INTEGER PRIMARY KEY AUTOINCREMENT,
			user_id INTEGER NOT NULL,
			token_hash TEXT NOT NULL,
			expires_at TIMESTAMP NOT NULL,
			revoked_at TIMESTAMP,
			created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
		)
	`).Error
	require.NoError(t, err)

	// Insérer seulement des sessions non expirées
	now := time.Now()
	futureTime1 := now.Add(24 * time.Hour)
	futureTime2 := now.Add(48 * time.Hour)

	err = gormDB.Exec(`
		INSERT INTO sessions (user_id, token_hash, expires_at, created_at)
		VALUES 
			(1, 'hash1', ?, ?),
			(1, 'hash2', ?, ?)
	`, futureTime1, now, futureTime2, now).Error
	require.NoError(t, err)

	sqlDB, err := gormDB.DB()
	require.NoError(t, err)

	testDB := &database.Database{
		DB: sqlDB,
	}

	logger := zap.NewNop()

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

	// Vérifier que toutes les sessions sont toujours là
	var count int64
	err = gormDB.Raw("SELECT COUNT(*) FROM sessions").Scan(&count).Error
	require.NoError(t, err)
	assert.Equal(t, int64(2), count, "All sessions should remain")
}

// TestCleanupExpiredSessions_EmptyDatabase teste le cas où la base de données est vide
func TestCleanupExpiredSessions_EmptyDatabase(t *testing.T) {
	// Créer une base de données de test
	gormDB, err := gorm.Open(sqlite.Open(":memory:"), &gorm.Config{})
	require.NoError(t, err)

	// Créer la table sessions
	err = gormDB.Exec(`
		CREATE TABLE sessions (
			id INTEGER PRIMARY KEY AUTOINCREMENT,
			user_id INTEGER NOT NULL,
			token_hash TEXT NOT NULL,
			expires_at TIMESTAMP NOT NULL,
			revoked_at TIMESTAMP,
			created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
		)
	`).Error
	require.NoError(t, err)

	sqlDB, err := gormDB.DB()
	require.NoError(t, err)

	testDB := &database.Database{
		DB: sqlDB,
	}

	logger := zap.NewNop()

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

	// Vérifier qu'il n'y a pas de sessions
	var count int64
	err = gormDB.Raw("SELECT COUNT(*) FROM sessions").Scan(&count).Error
	require.NoError(t, err)
	assert.Equal(t, int64(0), count, "No sessions should exist")
}

// TestScheduleCleanupJob_Execution teste que le job est programmé correctement
func TestScheduleCleanupJob_Execution(t *testing.T) {
	// Créer une base de données de test
	gormDB, err := gorm.Open(sqlite.Open(":memory:"), &gorm.Config{})
	require.NoError(t, err)

	// Créer la table sessions
	err = gormDB.Exec(`
		CREATE TABLE sessions (
			id INTEGER PRIMARY KEY AUTOINCREMENT,
			user_id INTEGER NOT NULL,
			token_hash TEXT NOT NULL,
			expires_at TIMESTAMP NOT NULL,
			revoked_at TIMESTAMP,
			created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
		)
	`).Error
	require.NoError(t, err)

	sqlDB, err := gormDB.DB()
	require.NoError(t, err)

	testDB := &database.Database{
		DB: sqlDB,
	}

	logger := zap.NewNop()

	// Programmer le job avec un ticker très court pour les tests (1 seconde au lieu de 24 heures)
	// Note: Dans un vrai test, on pourrait utiliser un mock ticker, mais pour simplifier
	// on teste juste que la fonction s'exécute sans erreur
	ticker := time.NewTicker(100 * time.Millisecond)
	defer ticker.Stop()

	executed := make(chan bool, 1)
	go func() {
		// Exécuter immédiatement au démarrage
		if err := CleanupExpiredSessions(testDB, logger); err != nil {
			logger.Error("Initial sessions cleanup job failed", zap.Error(err))
		}
		executed <- true

		// Attendre un tick
		<-ticker.C
		if err := CleanupExpiredSessions(testDB, logger); err != nil {
			logger.Error("Scheduled sessions cleanup job failed", zap.Error(err))
		}
		executed <- true
	}()

	// Attendre que le job initial soit exécuté
	select {
	case <-executed:
		assert.True(t, true, "Initial cleanup job should execute")
	case <-time.After(1 * time.Second):
		t.Fatal("Initial cleanup job did not execute in time")
	}

	// Attendre que le job programmé soit exécuté
	select {
	case <-executed:
		assert.True(t, true, "Scheduled cleanup job should execute")
	case <-time.After(2 * time.Second):
		t.Fatal("Scheduled cleanup job did not execute in time")
	}

	// Vérifier que la fonction ScheduleCleanupJob peut être appelée sans erreur
	// Note: On ne peut pas vraiment tester qu'elle s'exécute en continu sans bloquer le test
	ScheduleSessionCleanupJob(testDB, logger)
	time.Sleep(100 * time.Millisecond) // Attendre un peu pour que la goroutine démarre
}
