package database

import (
	"os"
	"testing"
	"time"

	"veza-backend-api/internal/models"

	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"
	"gorm.io/driver/sqlite"
	"gorm.io/gorm"
)

// TestSessionsTableMigration teste que le fichier de migration existe et peut être lu
func TestSessionsTableMigration(t *testing.T) {
	// Utiliser le chemin depuis le répertoire racine du projet
	// Le test peut être exécuté depuis différents répertoires
	migrationPath := "../../migrations/020_create_sessions.sql"

	// Essayer d'abord le chemin relatif depuis le répertoire de test
	content, err := os.ReadFile(migrationPath)
	if err != nil {
		// Si ça échoue, essayer depuis le répertoire racine
		migrationPath = "migrations/020_create_sessions.sql"
		content, err = os.ReadFile(migrationPath)
	}
	require.NoError(t, err, "Migration file should exist and be readable")

	// Vérifier que le contenu n'est pas vide
	assert.NotEmpty(t, content, "Migration file should not be empty")

	// Vérifier que le contenu contient les éléments essentiels
	contentStr := string(content)
	assert.Contains(t, contentStr, "CREATE TABLE", "Should create sessions table")
	assert.Contains(t, contentStr, "sessions", "Should create sessions table")
	// Note: user_id est BIGINT dans la migration 020, mais migré vers UUID dans 049
	assert.Contains(t, contentStr, "user_id", "Should have user_id column")
	assert.Contains(t, contentStr, "token_hash", "Should have token_hash column")
	assert.Contains(t, contentStr, "ip_address", "Should have ip_address column")
	assert.Contains(t, contentStr, "user_agent", "Should have user_agent column")
	assert.Contains(t, contentStr, "expires_at", "Should have expires_at column")
	assert.Contains(t, contentStr, "created_at", "Should have created_at column")
	assert.Contains(t, contentStr, "REFERENCES users(id) ON DELETE CASCADE", "Should have foreign key constraint")
	assert.Contains(t, contentStr, "idx_sessions_user_id", "Should have index on user_id")
	assert.Contains(t, contentStr, "idx_sessions_expires_at", "Should have index on expires_at")
	assert.Contains(t, contentStr, "idx_sessions_revoked_at", "Should have index on revoked_at")
}

// TestSessionsTable_Creation teste que la table sessions est créée correctement
func TestSessionsTable_Creation(t *testing.T) {
	// Créer une base de données en mémoire
	db, err := gorm.Open(sqlite.Open(":memory:"), &gorm.Config{})
	require.NoError(t, err, "Failed to open test database")

	// Créer la table users d'abord (requis pour la foreign key)
	err = db.AutoMigrate(&models.User{})
	require.NoError(t, err, "Failed to migrate users table")

	// Créer la table sessions manuellement (simule la migration SQL)
	// Note: SQLite stocke UUIDs comme TEXT, user_id est maintenant UUID (migration 049)
	err = db.Exec(`
		CREATE TABLE sessions (
			id TEXT PRIMARY KEY,
			user_id TEXT NOT NULL REFERENCES users(id) ON DELETE CASCADE,
			token_hash TEXT NOT NULL UNIQUE,
			ip_address TEXT,
			user_agent TEXT,
			expires_at TIMESTAMP NOT NULL,
			last_activity TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
			created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
		)
	`).Error
	require.NoError(t, err, "Failed to create sessions table")

	// Créer les index
	err = db.Exec("CREATE INDEX idx_sessions_user_id ON sessions(user_id)").Error
	require.NoError(t, err)
	err = db.Exec("CREATE INDEX idx_sessions_token_hash ON sessions(token_hash)").Error
	require.NoError(t, err)
	err = db.Exec("CREATE INDEX idx_sessions_expires_at ON sessions(expires_at)").Error
	require.NoError(t, err)

	// Vérifier que la table existe
	hasTable := db.Migrator().HasTable("sessions")
	assert.True(t, hasTable, "sessions table should exist")
}

// TestSessionsTable_Columns teste que toutes les colonnes sont présentes
func TestSessionsTable_Columns(t *testing.T) {
	// Créer une base de données en mémoire
	db, err := gorm.Open(sqlite.Open(":memory:"), &gorm.Config{})
	require.NoError(t, err)

	// Créer la table users
	err = db.AutoMigrate(&models.User{})
	require.NoError(t, err)

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

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

	// Vérifier que toutes les colonnes existent en insérant une session
	expiresAt := time.Now().Add(1 * time.Hour)
	err = db.Exec(`
		INSERT INTO sessions (user_id, token_hash, ip_address, user_agent, expires_at, last_activity, created_at)
		VALUES (?, ?, ?, ?, ?, ?, ?)
	`, user.ID, "test-token-hash-123", "192.168.1.1", "Mozilla/5.0", expiresAt, time.Now(), time.Now()).Error
	require.NoError(t, err, "Should be able to insert a session")

	// Vérifier que la session a été insérée
	var count int64
	err = db.Raw("SELECT COUNT(*) FROM sessions WHERE token_hash = ?", "test-token-hash-123").Scan(&count).Error
	require.NoError(t, err)
	assert.Equal(t, int64(1), count, "Session should be inserted")
}

// TestSessionsTable_ForeignKey teste que la foreign key fonctionne correctement
func TestSessionsTable_ForeignKey(t *testing.T) {
	// Créer une base de données en mémoire
	db, err := gorm.Open(sqlite.Open(":memory:"), &gorm.Config{})
	require.NoError(t, err)

	// Activer les foreign keys pour SQLite (requis pour CASCADE DELETE et validation FK)
	err = db.Exec("PRAGMA foreign_keys = ON").Error
	require.NoError(t, err)

	// Créer la table users
	err = db.AutoMigrate(&models.User{})
	require.NoError(t, err)

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

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

	// Insérer une session valide
	expiresAt := time.Now().Add(1 * time.Hour)
	err = db.Exec(`
		INSERT INTO sessions (user_id, token_hash, ip_address, user_agent, expires_at, last_activity, created_at)
		VALUES (?, ?, ?, ?, ?, ?, ?)
	`, user.ID, "valid-token-hash", "192.168.1.1", "Mozilla/5.0", expiresAt, time.Now(), time.Now()).Error
	require.NoError(t, err, "Should be able to insert session for existing user")

	// Tenter d'insérer une session avec un user_id inexistant (devrait échouer)
	// Utiliser un UUID valide mais inexistant
	fakeUserID := "00000000-0000-0000-0000-000000000999"
	err = db.Exec(`
		INSERT INTO sessions (user_id, token_hash, ip_address, user_agent, expires_at, last_activity, created_at)
		VALUES (?, ?, ?, ?, ?, ?, ?)
	`, fakeUserID, "invalid-token-hash", "192.168.1.1", "Mozilla/5.0", expiresAt, time.Now(), time.Now()).Error
	assert.Error(t, err, "Should not be able to insert session with non-existent user_id")

	// Vérifier que le CASCADE DELETE fonctionne
	// Utiliser Unscoped() pour forcer la suppression réelle (pas soft delete)
	err = db.Unscoped().Delete(user).Error
	require.NoError(t, err)

	// Vérifier que la session a été supprimée automatiquement
	var count int64
	err = db.Raw("SELECT COUNT(*) FROM sessions WHERE token_hash = ?", "valid-token-hash").Scan(&count).Error
	require.NoError(t, err)
	assert.Equal(t, int64(0), count, "Session should be deleted when user is deleted")
}

// TestSessionsTable_UniqueTokenHash teste que le token_hash doit être unique
func TestSessionsTable_UniqueTokenHash(t *testing.T) {
	// Créer une base de données en mémoire
	db, err := gorm.Open(sqlite.Open(":memory:"), &gorm.Config{})
	require.NoError(t, err)

	// Créer la table users
	err = db.AutoMigrate(&models.User{})
	require.NoError(t, err)

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

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

	// Insérer une session
	expiresAt := time.Now().Add(1 * time.Hour)
	err = db.Exec(`
		INSERT INTO sessions (user_id, token_hash, ip_address, user_agent, expires_at, last_activity, created_at)
		VALUES (?, ?, ?, ?, ?, ?, ?)
	`, user.ID, "unique-token-hash", "192.168.1.1", "Mozilla/5.0", expiresAt, time.Now(), time.Now()).Error
	require.NoError(t, err, "Should be able to insert first session")

	// Tenter d'insérer une session avec le même token_hash (devrait échouer)
	err = db.Exec(`
		INSERT INTO sessions (user_id, token_hash, ip_address, user_agent, expires_at, last_activity, created_at)
		VALUES (?, ?, ?, ?, ?, ?, ?)
	`, user.ID, "unique-token-hash", "192.168.1.2", "Chrome", expiresAt, time.Now(), time.Now()).Error
	assert.Error(t, err, "Should not be able to insert duplicate token_hash")
}

// TestSessionsTable_Indexes teste que les index sont créés correctement
func TestSessionsTable_Indexes(t *testing.T) {
	// Créer une base de données en mémoire
	db, err := gorm.Open(sqlite.Open(":memory:"), &gorm.Config{})
	require.NoError(t, err)

	// Créer la table users
	err = db.AutoMigrate(&models.User{})
	require.NoError(t, err)

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

	// Créer les index
	err = db.Exec("CREATE INDEX idx_sessions_user_id ON sessions(user_id)").Error
	require.NoError(t, err)
	err = db.Exec("CREATE INDEX idx_sessions_token_hash ON sessions(token_hash)").Error
	require.NoError(t, err)
	err = db.Exec("CREATE INDEX idx_sessions_expires_at ON sessions(expires_at)").Error
	require.NoError(t, err)

	// Vérifier que les index existent (SQLite stocke les index dans sqlite_master)
	var indexCount int64
	err = db.Raw(`
		SELECT COUNT(*) FROM sqlite_master 
		WHERE type='index' 
		AND name IN ('idx_sessions_user_id', 'idx_sessions_token_hash', 'idx_sessions_expires_at')
	`).Scan(&indexCount).Error
	require.NoError(t, err)
	assert.Equal(t, int64(3), indexCount, "All three indexes should exist")
}
