package database

import (
	"os"
	"testing"

	"veza-backend-api/internal/models"

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

// setupTestDB crée une base de données de test en mémoire
func setupTestDB(t *testing.T) *gorm.DB {
	db, err := gorm.Open(sqlite.Open(":memory:"), &gorm.Config{})
	require.NoError(t, err, "Failed to open test database")
	return db
}

// TestRunMigrations teste l'exécution des migrations GORM
func TestRunMigrations(t *testing.T) {
	db := setupTestDB(t)

	err := RunMigrations(db)
	assert.NoError(t, err, "RunMigrations should not return an error")

	// Vérifier que les tables existent
	assert.True(t, db.Migrator().HasTable(&models.User{}), "Users table should exist")
	assert.True(t, db.Migrator().HasTable(&models.RefreshToken{}), "RefreshTokens table should exist")
	assert.True(t, db.Migrator().HasTable(&models.Track{}), "Tracks table should exist")
	assert.True(t, db.Migrator().HasTable(&models.Playlist{}), "Playlists table should exist")
	assert.True(t, db.Migrator().HasTable(&models.PlaylistTrack{}), "PlaylistTracks table should exist")
	assert.True(t, db.Migrator().HasTable(&models.Message{}), "Messages table should exist")
	assert.True(t, db.Migrator().HasTable(&models.Room{}), "Rooms table should exist")
	assert.True(t, db.Migrator().HasTable(&models.RoomMember{}), "RoomMembers table should exist")
}

// TestRunMigrations_Idempotent teste que les migrations sont idempotentes
func TestRunMigrations_Idempotent(t *testing.T) {
	db := setupTestDB(t)

	// Exécuter les migrations deux fois
	err := RunMigrations(db)
	assert.NoError(t, err, "First RunMigrations should not return an error")

	err = RunMigrations(db)
	assert.NoError(t, err, "Second RunMigrations should not return an error")

	// Vérifier que les tables existent toujours
	assert.True(t, db.Migrator().HasTable(&models.User{}))
	assert.True(t, db.Migrator().HasTable(&models.Track{}))
}

// TestAddIndexes teste la création des indexes
func TestAddIndexes(t *testing.T) {
	db := setupTestDB(t)

	// Exécuter les migrations (qui incluent addIndexes)
	err := RunMigrations(db)
	require.NoError(t, err, "RunMigrations should succeed")

	// Pour SQLite, vérifier que les indexes existent en vérifiant les migrations
	// Note: SQLite stocke les indexes différemment de PostgreSQL
	// On vérifie plutôt que les migrations n'ont pas d'erreur
	// et que les tables peuvent être créées avec les indexes

	// Vérifier que les tables ont bien les colonnes indexées
	var user models.User
	// Vérifier que l'index existe (HasIndex retourne un bool, pas une erreur)
	hasIndex := db.Migrator().HasIndex(&user, "idx_users_email")
	// SQLite peut avoir un comportement différent, donc on accepte les deux cas
	// L'important est que la migration fonctionne sans erreur
	_ = hasIndex

	// Vérifier qu'on peut créer un utilisateur (ce qui teste les contraintes)
	user = models.User{
		Username: "testuser",
		Email:    "test@example.com",
		Role:     "user",
	}
	err = db.Create(&user).Error
	assert.NoError(t, err, "Should be able to create a user")

	// Vérifier qu'on ne peut pas créer un utilisateur avec un email dupliqué
	user2 := models.User{
		Username: "testuser2",
		Email:    "test@example.com",
		Role:     "user",
	}
	err = db.Create(&user2).Error
	assert.Error(t, err, "Should not be able to create user with duplicate email")
}

// TestMigrations_UserRelations teste les relations entre User et autres modèles
func TestMigrations_UserRelations(t *testing.T) {
	db := setupTestDB(t)

	err := RunMigrations(db)
	require.NoError(t, err)

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

	// Créer un refresh token pour cet utilisateur
	refreshToken := models.RefreshToken{
		UserID:    user.ID,
		TokenHash: "hash123",
		ExpiresAt: db.NowFunc().AddDate(0, 0, 7),
	}
	err = db.Create(&refreshToken).Error
	assert.NoError(t, err, "Should be able to create refresh token")

	// Vérifier que la relation fonctionne
	var retrievedToken models.RefreshToken
	err = db.First(&retrievedToken, refreshToken.ID).Error
	assert.NoError(t, err)
	assert.Equal(t, user.ID, retrievedToken.UserID)
}

// TestMigrations_TrackRelations teste les relations entre Track et User
func TestMigrations_TrackRelations(t *testing.T) {
	db := setupTestDB(t)

	err := RunMigrations(db)
	require.NoError(t, err)

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

	// Créer une track pour cet utilisateur
	track := models.Track{
		UserID:   user.ID,
		Title:    "Test Track",
		Duration: 180,
	}
	err = db.Create(&track).Error
	assert.NoError(t, err, "Should be able to create track")

	// Vérifier que la relation fonctionne
	var retrievedTrack models.Track
	err = db.First(&retrievedTrack, track.ID).Error
	assert.NoError(t, err)
	assert.Equal(t, user.ID, retrievedTrack.UserID)
}

// TestMigrations_PlaylistRelations teste les relations pour les playlists
func TestMigrations_PlaylistRelations(t *testing.T) {
	db := setupTestDB(t)

	err := RunMigrations(db)
	require.NoError(t, err)

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

	// Créer une playlist
	playlist := models.Playlist{
		UserID: user.ID,
		Title:  "My Playlist",
	}
	err = db.Create(&playlist).Error
	require.NoError(t, err)

	// Créer une track
	track := models.Track{
		UserID:   user.ID,
		Title:    "Track 1",
		Duration: 200,
	}
	err = db.Create(&track).Error
	require.NoError(t, err)

	// Ajouter la track à la playlist
	playlistTrack := models.PlaylistTrack{
		PlaylistID: playlist.ID,
		TrackID:    track.ID,
		Position:   1,
	}
	err = db.Create(&playlistTrack).Error
	assert.NoError(t, err, "Should be able to add track to playlist")

	// Vérifier la relation
	var retrievedPlaylist models.Playlist
	err = db.Preload("Tracks").First(&retrievedPlaylist, playlist.ID).Error
	assert.NoError(t, err)
	assert.Len(t, retrievedPlaylist.Tracks, 1)
}

// TestMigrations_RoomRelations teste les relations pour les rooms et messages
func TestMigrations_RoomRelations(t *testing.T) {
	db := setupTestDB(t)

	err := RunMigrations(db)
	require.NoError(t, err)

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

	// Créer une room
	room := models.Room{
		Name:      "Test Room",
		Type:      "public",
		CreatedBy: user.ID,
	}
	err = db.Create(&room).Error
	require.NoError(t, err)

	// Ajouter l'utilisateur à la room
	roomMember := models.RoomMember{
		RoomID: room.ID,
		UserID: user.ID,
		Role:   "owner",
	}
	err = db.Create(&roomMember).Error
	assert.NoError(t, err, "Should be able to add user to room")

	// Créer un message dans la room
	message := models.Message{
		RoomID:  room.ID,
		UserID:  user.ID,
		Content: "Hello, world!",
		Type:    "text",
	}
	err = db.Create(&message).Error
	assert.NoError(t, err, "Should be able to create message")

	// Vérifier les relations
	var retrievedRoom models.Room
	err = db.Preload("Members").Preload("Messages").First(&retrievedRoom, room.ID).Error
	assert.NoError(t, err)
	assert.Len(t, retrievedRoom.Members, 1)
	assert.Len(t, retrievedRoom.Messages, 1)
}

// TestEmailVerificationTokensMigration teste que la migration pour la table email_verification_tokens existe et peut être lue
func TestEmailVerificationTokensMigration(t *testing.T) {
	migrationPath := "migrations/018_create_email_verification_tokens.sql"

	// Vérifier que le fichier existe
	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 email_verification_tokens", "Should create email_verification_tokens table")
	assert.Contains(t, contentStr, "user_id BIGINT", "Should have user_id column")
	assert.Contains(t, contentStr, "token VARCHAR(255)", "Should have token column")
	assert.Contains(t, contentStr, "expires_at TIMESTAMP", "Should have expires_at column")
	assert.Contains(t, contentStr, "used BOOLEAN", "Should have used column")
	assert.Contains(t, contentStr, "REFERENCES users(id) ON DELETE CASCADE", "Should have foreign key constraint")
	assert.Contains(t, contentStr, "idx_email_verification_tokens_token", "Should have index on token")
	assert.Contains(t, contentStr, "idx_email_verification_tokens_user_id", "Should have index on user_id")
	assert.Contains(t, contentStr, "idx_email_verification_tokens_expires_at", "Should have index on expires_at")
}
