package transactions

import (
	"context"
	"testing"

	"veza-backend-api/internal/models"
	"veza-backend-api/internal/repositories"
	"veza-backend-api/internal/services"
	"veza-backend-api/internal/testutils"

	"github.com/google/uuid"
	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"
	"go.uber.org/zap/zaptest"
	"gorm.io/driver/postgres"
	"gorm.io/gorm"
)

// setupTestDB crée une DB de test avec testcontainers
func setupTestDBForPlaylist(t *testing.T) *gorm.DB {
	ctx := context.Background()
	dsn, err := testutils.GetTestContainerDB(ctx)
	require.NoError(t, err, "Failed to setup test database")

	db, err := gorm.Open(postgres.Open(dsn), &gorm.Config{})
	require.NoError(t, err, "Failed to open database connection")

	// Note: Les migrations SQL sont déjà exécutées lors du démarrage du conteneur PostgreSQL
	// via postgres.WithInitScripts dans internal/testutils/setup.go
	// AutoMigrate n'est pas nécessaire et peut causer des erreurs avec les vues existantes

	return db
}

// cleanupTestDB nettoie la DB entre les tests
func cleanupTestDBForPlaylist(_ *testing.T, db *gorm.DB) {
	db.Exec("TRUNCATE TABLE playlist_tracks CASCADE")
	db.Exec("TRUNCATE TABLE playlist_collaborators CASCADE")
	db.Exec("TRUNCATE TABLE playlists CASCADE")
	db.Exec("TRUNCATE TABLE tracks CASCADE")
	db.Exec("TRUNCATE TABLE users CASCADE")
}

// createTestUser crée un utilisateur de test
func createTestUserForPlaylist(t *testing.T, db *gorm.DB) *models.User {
	user := &models.User{
		Username:     "testuser_" + uuid.New().String()[:8],
		Slug:         "testuser_" + uuid.New().String()[:8], // Unique slug
		Email:        "test_" + uuid.New().String()[:8] + "@example.com",
		PasswordHash: "$2a$10$examplehash",
		IsActive:     true,
		IsVerified:   true,
	}
	err := db.Create(user).Error
	require.NoError(t, err)
	return user
}

// createTestTrack crée un track de test
func createTestTrack(t *testing.T, db *gorm.DB, userID uuid.UUID) uuid.UUID {
	// Create a dummy file first (required by FK)
	fileID := uuid.New()
	err := db.Exec(`
		INSERT INTO files (id, user_id, filename, original_filename, mime_type, file_size, storage_path, url, is_public)
		VALUES (?, ?, 'test_track.mp3', 'test_track.mp3', 'audio/mpeg', 5242880, '/test/track.mp3', 'http://example.com/test.mp3', true)
	`, fileID, userID).Error
	require.NoError(t, err, "Failed to create dummy file for track")

	trackID := uuid.New()
	track := &models.Track{
		ID:       trackID,
		UserID:   userID,  // Maps to creator_id now
		FileID:   &fileID, // FileID is *uuid.UUID, need pointer
		Title:    "Test Track " + uuid.New().String()[:8],
		Artist:   "Test Artist",
		Duration: 180,
		FilePath: "/test/track.mp3",
		FileSize: 5242880,
		Format:   "mp3",
		IsPublic: true,
		Status:   "completed",
	}
	err = db.Create(track).Error
	require.NoError(t, err)
	return track.ID
}

// createTestPlaylistWithTracks crée une playlist avec des tracks
func createTestPlaylistWithTracks(t *testing.T, db *gorm.DB, userID uuid.UUID, trackCount int) *models.Playlist {
	playlist := &models.Playlist{
		UserID:      userID,
		Title:       "Original Playlist",
		Description: "Test playlist",
		IsPublic:    false,
		TrackCount:  0,
	}
	err := db.Create(playlist).Error
	require.NoError(t, err)

	tracks := make([]uuid.UUID, trackCount)
	for i := 0; i < trackCount; i++ {
		tracks[i] = createTestTrack(t, db, userID)
	}

	// Ajouter les tracks à la playlist
	for i, trackID := range tracks {
		playlistTrack := &models.PlaylistTrack{
			PlaylistID: playlist.ID,
			TrackID:    trackID,
			Position:   i + 1,
			AddedBy:    userID,
		}
		err := db.Create(playlistTrack).Error
		require.NoError(t, err)
	}

	// Mettre à jour le compteur
	playlist.TrackCount = trackCount
	db.Model(playlist).Update("track_count", trackCount)

	return playlist
}

// TestDuplicatePlaylist_Success vérifie que la duplication fonctionne correctement
func TestDuplicatePlaylist_Success(t *testing.T) {
	db := setupTestDBForPlaylist(t)
	defer cleanupTestDBForPlaylist(t, db)

	logger := zaptest.NewLogger(t)
	// Create repositories
	playlistRepo := repositories.NewPlaylistRepository(db)
	playlistTrackRepo := repositories.NewPlaylistTrackRepository(db)
	playlistCollaboratorRepo := repositories.NewPlaylistCollaboratorRepository(db)
	userRepo := repositories.NewGormUserRepository(db)

	playlistService := services.NewPlaylistService(playlistRepo, playlistTrackRepo, playlistCollaboratorRepo, userRepo, logger)
	duplicateService := services.NewPlaylistDuplicateService(playlistService, db, logger)

	user := createTestUserForPlaylist(t, db)
	originalPlaylist := createTestPlaylistWithTracks(t, db, user.ID, 5)

	// Dupliquer la playlist
	request := services.DuplicatePlaylistRequest{
		NewTitle: "Duplicated Playlist",
	}
	newPlaylist, err := duplicateService.DuplicatePlaylist(
		context.Background(),
		originalPlaylist.ID,
		user.ID,
		request,
	)
	require.NoError(t, err, "DuplicatePlaylist should succeed")
	require.NotNil(t, newPlaylist, "New playlist should be created")

	// Vérifier que la nouvelle playlist existe
	var playlistCount int64
	db.Model(&models.Playlist{}).Where("id = ?", newPlaylist.ID).Count(&playlistCount)
	assert.Equal(t, int64(1), playlistCount, "New playlist should exist")

	// Vérifier que tous les tracks sont dupliqués
	var trackCount int64
	db.Model(&models.PlaylistTrack{}).
		Where("playlist_id = ?", newPlaylist.ID).
		Count(&trackCount)
	assert.Equal(t, int64(5), trackCount, "All tracks should be duplicated")

	// Vérifier que le compteur est cohérent
	assert.Equal(t, int(5), newPlaylist.TrackCount, "Track count should match")
}

// TestDuplicatePlaylist_RollbackOnPlaylistNotFound vérifie le rollback si la playlist n'existe pas
func TestDuplicatePlaylist_RollbackOnPlaylistNotFound(t *testing.T) {
	db := setupTestDBForPlaylist(t)
	defer cleanupTestDBForPlaylist(t, db)

	logger := zaptest.NewLogger(t)
	// Create repositories
	playlistRepo := repositories.NewPlaylistRepository(db)
	playlistTrackRepo := repositories.NewPlaylistTrackRepository(db)
	playlistCollaboratorRepo := repositories.NewPlaylistCollaboratorRepository(db)
	userRepo := repositories.NewGormUserRepository(db)

	playlistService := services.NewPlaylistService(playlistRepo, playlistTrackRepo, playlistCollaboratorRepo, userRepo, logger)
	duplicateService := services.NewPlaylistDuplicateService(playlistService, db, logger)

	user := createTestUserForPlaylist(t, db)
	fakePlaylistID := uuid.New()

	request := services.DuplicatePlaylistRequest{
		NewTitle: "Duplicated Playlist",
	}
	_, err := duplicateService.DuplicatePlaylist(
		context.Background(),
		fakePlaylistID,
		user.ID,
		request,
	)
	require.Error(t, err, "DuplicatePlaylist should fail")
	assert.Contains(t, err.Error(), "playlist not found", "Error should mention playlist not found")

	// Vérifier qu'aucune playlist n'a été créée
	var playlistCount int64
	db.Model(&models.Playlist{}).Where("user_id = ?", user.ID).Count(&playlistCount)
	assert.Equal(t, int64(0), playlistCount, "No playlist should be created on error")
}

// TestDuplicatePlaylist_RollbackOnTrackError vérifie le rollback si un track échoue
func TestDuplicatePlaylist_RollbackOnTrackError(t *testing.T) {
	db := setupTestDBForPlaylist(t)
	defer cleanupTestDBForPlaylist(t, db)

	logger := zaptest.NewLogger(t)
	// Create repositories
	playlistRepo := repositories.NewPlaylistRepository(db)
	playlistTrackRepo := repositories.NewPlaylistTrackRepository(db)
	playlistCollaboratorRepo := repositories.NewPlaylistCollaboratorRepository(db)
	userRepo := repositories.NewGormUserRepository(db)

	playlistService := services.NewPlaylistService(playlistRepo, playlistTrackRepo, playlistCollaboratorRepo, userRepo, logger)
	duplicateService := services.NewPlaylistDuplicateService(playlistService, db, logger)

	user := createTestUserForPlaylist(t, db)
	originalPlaylist := createTestPlaylistWithTracks(t, db, user.ID, 3)

	// Supprimer un track pour forcer une erreur FK lors de la duplication
	// (simulation d'une erreur au milieu de la transaction)
	var firstPlaylistTrack models.PlaylistTrack
	if err := db.Where("playlist_id = ?", originalPlaylist.ID).
		Order("position ASC").
		Limit(1).
		First(&firstPlaylistTrack).Error; err != nil {
		t.Fatalf("Failed to load first playlist track: %v", err)
	}

	// Charger le track à partir de son ID
	var firstTrack models.Track
	if err := db.First(&firstTrack, "id = ?", firstPlaylistTrack.TrackID).Error; err != nil {
		t.Fatalf("Failed to load track: %v", err)
	}

	// Supprimer le track (soft delete)
	if err := db.Delete(&firstTrack).Error; err != nil {
		t.Fatalf("Failed to delete track: %v", err)
	}

	// Tenter de dupliquer (devrait échouer car le track n'existe plus)
	request := services.DuplicatePlaylistRequest{
		NewTitle: "Duplicated Playlist",
	}
	_, err := duplicateService.DuplicatePlaylist(
		context.Background(),
		originalPlaylist.ID,
		user.ID,
		request,
	)
	require.Error(t, err, "DuplicatePlaylist should fail")

	// Vérifier qu'aucune playlist n'a été créée (rollback complet)
	var playlistCount int64
	db.Model(&models.Playlist{}).
		Where("user_id = ? AND title = ?", user.ID, "Duplicated Playlist").
		Count(&playlistCount)
	assert.Equal(t, int64(0), playlistCount, "No playlist should be created on error")

	// Vérifier qu'aucun track n'a été ajouté
	var trackCount int64
	db.Model(&models.PlaylistTrack{}).
		Where("playlist_id != ?", originalPlaylist.ID).
		Count(&trackCount)
	assert.Equal(t, int64(0), trackCount, "No tracks should be created on error")
}

// TestDuplicatePlaylist_Coherence vérifie la cohérence des données après duplication
func TestDuplicatePlaylist_Coherence(t *testing.T) {
	db := setupTestDBForPlaylist(t)
	defer cleanupTestDBForPlaylist(t, db)

	logger := zaptest.NewLogger(t)
	// Create repositories
	playlistRepo := repositories.NewPlaylistRepository(db)
	playlistTrackRepo := repositories.NewPlaylistTrackRepository(db)
	playlistCollaboratorRepo := repositories.NewPlaylistCollaboratorRepository(db)
	userRepo := repositories.NewGormUserRepository(db)

	playlistService := services.NewPlaylistService(playlistRepo, playlistTrackRepo, playlistCollaboratorRepo, userRepo, logger)
	duplicateService := services.NewPlaylistDuplicateService(playlistService, db, logger)

	user := createTestUserForPlaylist(t, db)
	originalPlaylist := createTestPlaylistWithTracks(t, db, user.ID, 10)

	// Dupliquer
	request := services.DuplicatePlaylistRequest{
		NewTitle: "Duplicated Playlist",
	}
	newPlaylist, err := duplicateService.DuplicatePlaylist(
		context.Background(),
		originalPlaylist.ID,
		user.ID,
		request,
	)
	require.NoError(t, err)

	// Vérifier que le compteur correspond au nombre réel de tracks
	var actualTrackCount int64
	db.Model(&models.PlaylistTrack{}).
		Where("playlist_id = ?", newPlaylist.ID).
		Count(&actualTrackCount)
	assert.Equal(t, int64(newPlaylist.TrackCount), actualTrackCount, "Track count should match actual tracks")

	// Vérifier que les positions sont cohérentes
	var playlistTracks []models.PlaylistTrack
	db.Where("playlist_id = ?", newPlaylist.ID).
		Order("position ASC").
		Find(&playlistTracks)

	for i, pt := range playlistTracks {
		assert.Equal(t, i+1, pt.Position, "Position should be sequential")
	}
}

// TestDuplicatePlaylist_EmptyPlaylist vérifie la duplication d'une playlist vide
func TestDuplicatePlaylist_EmptyPlaylist(t *testing.T) {
	db := setupTestDBForPlaylist(t)
	defer cleanupTestDBForPlaylist(t, db)

	logger := zaptest.NewLogger(t)
	// Create repositories
	playlistRepo := repositories.NewPlaylistRepository(db)
	playlistTrackRepo := repositories.NewPlaylistTrackRepository(db)
	playlistCollaboratorRepo := repositories.NewPlaylistCollaboratorRepository(db)
	userRepo := repositories.NewGormUserRepository(db)

	playlistService := services.NewPlaylistService(playlistRepo, playlistTrackRepo, playlistCollaboratorRepo, userRepo, logger)
	duplicateService := services.NewPlaylistDuplicateService(playlistService, db, logger)

	user := createTestUserForPlaylist(t, db)
	originalPlaylist := createTestPlaylistWithTracks(t, db, user.ID, 0) // Playlist vide

	request := services.DuplicatePlaylistRequest{
		NewTitle: "Duplicated Empty Playlist",
	}
	newPlaylist, err := duplicateService.DuplicatePlaylist(
		context.Background(),
		originalPlaylist.ID,
		user.ID,
		request,
	)
	require.NoError(t, err, "Duplicating empty playlist should succeed")
	assert.Equal(t, 0, newPlaylist.TrackCount, "Empty playlist should have 0 tracks")

	// Vérifier qu'aucun track n'a été créé
	var trackCount int64
	db.Model(&models.PlaylistTrack{}).
		Where("playlist_id = ?", newPlaylist.ID).
		Count(&trackCount)
	assert.Equal(t, int64(0), trackCount, "No tracks should be created for empty playlist")
}
