package services

import (
	"context"
	"testing"

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

func setupTestTrackServiceForList(t *testing.T) (*TrackService, *gorm.DB, func()) {
	// Setup in-memory SQLite database
	db, err := gorm.Open(sqlite.Open(":memory:"), &gorm.Config{})
	assert.NoError(t, err)

	// Auto-migrate
	err = db.AutoMigrate(&models.Track{}, &models.User{})
	assert.NoError(t, err)

	// Create test users
	user1 := &models.User{
		ID:       123,
		Username: "testuser1",
		Email:    "test1@example.com",
		IsActive: true,
	}
	err = db.Create(user1).Error
	assert.NoError(t, err)

	user2 := &models.User{
		ID:       456,
		Username: "testuser2",
		Email:    "test2@example.com",
		IsActive: true,
	}
	err = db.Create(user2).Error
	assert.NoError(t, err)

	// Setup logger
	logger := zap.NewNop()

	// Setup test service
	service := NewTrackService(db, logger, "test_uploads/tracks")

	// Cleanup function
	cleanup := func() {
		// Database will be closed automatically
	}

	return service, db, cleanup
}

func TestTrackService_ListTracks_Success(t *testing.T) {
	service, db, cleanup := setupTestTrackServiceForList(t)
	defer cleanup()

	ctx := context.Background()

	// Créer quelques tracks avec statut completed
	track1 := &models.Track{
		UserID:   123,
		Title:    "Track 1",
		FilePath: "/test/track1.mp3",
		FileSize: 5 * 1024 * 1024,
		Format:   "MP3",
		Genre:    "Rock",
		Duration: 180,
		IsPublic: true,
		Status:   models.TrackStatusCompleted,
		PlayCount: 10,
		LikeCount: 5,
	}
	err := db.Create(track1).Error
	assert.NoError(t, err)

	track2 := &models.Track{
		UserID:   123,
		Title:    "Track 2",
		FilePath: "/test/track2.flac",
		FileSize: 10 * 1024 * 1024,
		Format:   "FLAC",
		Genre:    "Jazz",
		Duration: 200,
		IsPublic: true,
		Status:   models.TrackStatusCompleted,
		PlayCount: 20,
		LikeCount: 10,
	}
	err = db.Create(track2).Error
	assert.NoError(t, err)

	track3 := &models.Track{
		UserID:   456,
		Title:    "Track 3",
		FilePath: "/test/track3.mp3",
		FileSize: 3 * 1024 * 1024,
		Format:   "MP3",
		Genre:    "Rock",
		Duration: 150,
		IsPublic: true,
		Status:   models.TrackStatusCompleted,
		PlayCount: 5,
		LikeCount: 2,
	}
	err = db.Create(track3).Error
	assert.NoError(t, err)

	// Track avec statut uploading (ne doit pas apparaître)
	track4 := &models.Track{
		UserID:   123,
		Title:    "Track 4",
		FilePath: "/test/track4.mp3",
		FileSize: 2 * 1024 * 1024,
		Format:   "MP3",
		Duration: 100,
		IsPublic: true,
		Status:   models.TrackStatusUploading,
	}
	err = db.Create(track4).Error
	assert.NoError(t, err)

	// Test: Lister tous les tracks
	params := TrackListParams{
		Page:      1,
		Limit:     20,
		SortBy:    "created_at",
		SortOrder: "desc",
	}
	tracks, total, err := service.ListTracks(ctx, params)

	assert.NoError(t, err)
	assert.Equal(t, int64(3), total) // Seulement les tracks completed
	assert.Len(t, tracks, 3)
}

func TestTrackService_ListTracks_WithPagination(t *testing.T) {
	service, db, cleanup := setupTestTrackServiceForList(t)
	defer cleanup()

	ctx := context.Background()

	// Créer 5 tracks
	for i := 1; i <= 5; i++ {
		track := &models.Track{
			UserID:   123,
			Title:    "Track " + string(rune('0'+i)),
			FilePath: "/test/track" + string(rune('0'+i)) + ".mp3",
			FileSize: 5 * 1024 * 1024,
			Format:   "MP3",
			Duration: 180,
			IsPublic: true,
			Status:   models.TrackStatusCompleted,
		}
		err := db.Create(track).Error
		assert.NoError(t, err)
	}

	// Test: Page 1, limit 2
	params := TrackListParams{
		Page:      1,
		Limit:     2,
		SortBy:    "created_at",
		SortOrder: "desc",
	}
	tracks, total, err := service.ListTracks(ctx, params)

	assert.NoError(t, err)
	assert.Equal(t, int64(5), total)
	assert.Len(t, tracks, 2)

	// Test: Page 2, limit 2
	params.Page = 2
	tracks, total, err = service.ListTracks(ctx, params)

	assert.NoError(t, err)
	assert.Equal(t, int64(5), total)
	assert.Len(t, tracks, 2)

	// Test: Page 3, limit 2
	params.Page = 3
	tracks, total, err = service.ListTracks(ctx, params)

	assert.NoError(t, err)
	assert.Equal(t, int64(5), total)
	assert.Len(t, tracks, 1) // Dernière page avec 1 track
}

func TestTrackService_ListTracks_WithUserFilter(t *testing.T) {
	service, db, cleanup := setupTestTrackServiceForList(t)
	defer cleanup()

	ctx := context.Background()

	// Créer tracks pour deux utilisateurs
	track1 := &models.Track{
		UserID:   123,
		Title:    "Track User 1",
		FilePath: "/test/track1.mp3",
		FileSize: 5 * 1024 * 1024,
		Format:   "MP3",
		Duration: 180,
		IsPublic: true,
		Status:   models.TrackStatusCompleted,
	}
	err := db.Create(track1).Error
	assert.NoError(t, err)

	track2 := &models.Track{
		UserID:   456,
		Title:    "Track User 2",
		FilePath: "/test/track2.mp3",
		FileSize: 5 * 1024 * 1024,
		Format:   "MP3",
		Duration: 180,
		IsPublic: true,
		Status:   models.TrackStatusCompleted,
	}
	err = db.Create(track2).Error
	assert.NoError(t, err)

	// Test: Filtrer par user_id
	userID := int64(123)
	params := TrackListParams{
		Page:      1,
		Limit:     20,
		UserID:    &userID,
		SortBy:    "created_at",
		SortOrder: "desc",
	}
	tracks, total, err := service.ListTracks(ctx, params)

	assert.NoError(t, err)
	assert.Equal(t, int64(1), total)
	assert.Len(t, tracks, 1)
	assert.Equal(t, int64(123), tracks[0].UserID)
}

func TestTrackService_ListTracks_WithGenreFilter(t *testing.T) {
	service, db, cleanup := setupTestTrackServiceForList(t)
	defer cleanup()

	ctx := context.Background()

	// Créer tracks avec différents genres
	track1 := &models.Track{
		UserID:   123,
		Title:    "Rock Track",
		FilePath: "/test/track1.mp3",
		FileSize: 5 * 1024 * 1024,
		Format:   "MP3",
		Genre:    "Rock",
		Duration: 180,
		IsPublic: true,
		Status:   models.TrackStatusCompleted,
	}
	err := db.Create(track1).Error
	assert.NoError(t, err)

	track2 := &models.Track{
		UserID:   123,
		Title:    "Jazz Track",
		FilePath: "/test/track2.mp3",
		FileSize: 5 * 1024 * 1024,
		Format:   "MP3",
		Genre:    "Jazz",
		Duration: 180,
		IsPublic: true,
		Status:   models.TrackStatusCompleted,
	}
	err = db.Create(track2).Error
	assert.NoError(t, err)

	// Test: Filtrer par genre
	genre := "Rock"
	params := TrackListParams{
		Page:      1,
		Limit:     20,
		Genre:     &genre,
		SortBy:    "created_at",
		SortOrder: "desc",
	}
	tracks, total, err := service.ListTracks(ctx, params)

	assert.NoError(t, err)
	assert.Equal(t, int64(1), total)
	assert.Len(t, tracks, 1)
	assert.Equal(t, "Rock", tracks[0].Genre)
}

func TestTrackService_ListTracks_WithFormatFilter(t *testing.T) {
	service, db, cleanup := setupTestTrackServiceForList(t)
	defer cleanup()

	ctx := context.Background()

	// Créer tracks avec différents formats
	track1 := &models.Track{
		UserID:   123,
		Title:    "MP3 Track",
		FilePath: "/test/track1.mp3",
		FileSize: 5 * 1024 * 1024,
		Format:   "MP3",
		Duration: 180,
		IsPublic: true,
		Status:   models.TrackStatusCompleted,
	}
	err := db.Create(track1).Error
	assert.NoError(t, err)

	track2 := &models.Track{
		UserID:   123,
		Title:    "FLAC Track",
		FilePath: "/test/track2.flac",
		FileSize: 10 * 1024 * 1024,
		Format:   "FLAC",
		Duration: 200,
		IsPublic: true,
		Status:   models.TrackStatusCompleted,
	}
	err = db.Create(track2).Error
	assert.NoError(t, err)

	// Test: Filtrer par format
	format := "FLAC"
	params := TrackListParams{
		Page:      1,
		Limit:     20,
		Format:    &format,
		SortBy:    "created_at",
		SortOrder: "desc",
	}
	tracks, total, err := service.ListTracks(ctx, params)

	assert.NoError(t, err)
	assert.Equal(t, int64(1), total)
	assert.Len(t, tracks, 1)
	assert.Equal(t, "FLAC", tracks[0].Format)
}

func TestTrackService_ListTracks_WithSorting(t *testing.T) {
	service, db, cleanup := setupTestTrackServiceForList(t)
	defer cleanup()

	ctx := context.Background()

	// Créer tracks avec différents titres
	track1 := &models.Track{
		UserID:   123,
		Title:    "A Track",
		FilePath: "/test/track1.mp3",
		FileSize: 5 * 1024 * 1024,
		Format:   "MP3",
		Duration: 180,
		IsPublic: true,
		Status:   models.TrackStatusCompleted,
	}
	err := db.Create(track1).Error
	assert.NoError(t, err)

	track2 := &models.Track{
		UserID:   123,
		Title:    "Z Track",
		FilePath: "/test/track2.mp3",
		FileSize: 5 * 1024 * 1024,
		Format:   "MP3",
		Duration: 180,
		IsPublic: true,
		Status:   models.TrackStatusCompleted,
	}
	err = db.Create(track2).Error
	assert.NoError(t, err)

	track3 := &models.Track{
		UserID:   123,
		Title:    "M Track",
		FilePath: "/test/track3.mp3",
		FileSize: 5 * 1024 * 1024,
		Format:   "MP3",
		Duration: 180,
		IsPublic: true,
		Status:   models.TrackStatusCompleted,
	}
	err = db.Create(track3).Error
	assert.NoError(t, err)

	// Test: Trier par titre asc
	params := TrackListParams{
		Page:      1,
		Limit:     20,
		SortBy:    "title",
		SortOrder: "asc",
	}
	tracks, total, err := service.ListTracks(ctx, params)

	assert.NoError(t, err)
	assert.Equal(t, int64(3), total)
	assert.Len(t, tracks, 3)
	assert.Equal(t, "A Track", tracks[0].Title)
	assert.Equal(t, "M Track", tracks[1].Title)
	assert.Equal(t, "Z Track", tracks[2].Title)
}

func TestTrackService_ListTracks_WithPopularitySort(t *testing.T) {
	service, db, cleanup := setupTestTrackServiceForList(t)
	defer cleanup()

	ctx := context.Background()

	// Créer tracks avec différentes popularités
	track1 := &models.Track{
		UserID:    123,
		Title:     "Low Popularity",
		FilePath:  "/test/track1.mp3",
		FileSize:  5 * 1024 * 1024,
		Format:    "MP3",
		Duration:  180,
		IsPublic:  true,
		Status:    models.TrackStatusCompleted,
		PlayCount: 5,
		LikeCount: 2,
	}
	err := db.Create(track1).Error
	assert.NoError(t, err)

	track2 := &models.Track{
		UserID:    123,
		Title:     "High Popularity",
		FilePath:  "/test/track2.mp3",
		FileSize:  5 * 1024 * 1024,
		Format:    "MP3",
		Duration:  180,
		IsPublic:  true,
		Status:    models.TrackStatusCompleted,
		PlayCount: 50,
		LikeCount: 20,
	}
	err = db.Create(track2).Error
	assert.NoError(t, err)

	track3 := &models.Track{
		UserID:    123,
		Title:     "Medium Popularity",
		FilePath:  "/test/track3.mp3",
		FileSize:  5 * 1024 * 1024,
		Format:    "MP3",
		Duration:  180,
		IsPublic:  true,
		Status:    models.TrackStatusCompleted,
		PlayCount: 20,
		LikeCount: 10,
	}
	err = db.Create(track3).Error
	assert.NoError(t, err)

	// Test: Trier par popularité desc
	params := TrackListParams{
		Page:      1,
		Limit:     20,
		SortBy:    "popularity",
		SortOrder: "desc",
	}
	tracks, total, err := service.ListTracks(ctx, params)

	assert.NoError(t, err)
	assert.Equal(t, int64(3), total)
	assert.Len(t, tracks, 3)
	// Vérifier que le plus populaire est en premier (70 = 50 + 20)
	assert.Equal(t, "High Popularity", tracks[0].Title)
}

func TestTrackService_ListTracks_DefaultValues(t *testing.T) {
	service, db, cleanup := setupTestTrackServiceForList(t)
	defer cleanup()

	ctx := context.Background()

	// Créer un track
	track := &models.Track{
		UserID:   123,
		Title:    "Track",
		FilePath: "/test/track.mp3",
		FileSize: 5 * 1024 * 1024,
		Format:   "MP3",
		Duration: 180,
		IsPublic: true,
		Status:   models.TrackStatusCompleted,
	}
	err := db.Create(track).Error
	assert.NoError(t, err)

	// Test: Paramètres par défaut (page 0, limit 0)
	params := TrackListParams{
		Page:  0,
		Limit: 0,
	}
	tracks, total, err := service.ListTracks(ctx, params)

	assert.NoError(t, err)
	assert.Equal(t, int64(1), total)
	assert.Len(t, tracks, 1)
	// Vérifier que les valeurs par défaut sont appliquées
	// Page devrait être 1, limit devrait être 20
}

func TestTrackService_ListTracks_MaxLimit(t *testing.T) {
	service, db, cleanup := setupTestTrackServiceForList(t)
	defer cleanup()

	ctx := context.Background()

	// Créer 150 tracks
	for i := 1; i <= 150; i++ {
		track := &models.Track{
			UserID:   123,
			Title:    "Track " + string(rune('0'+(i%10))),
			FilePath: "/test/track" + string(rune('0'+(i%10))) + ".mp3",
			FileSize: 5 * 1024 * 1024,
			Format:   "MP3",
			Duration: 180,
			IsPublic: true,
			Status:   models.TrackStatusCompleted,
		}
		err := db.Create(track).Error
		assert.NoError(t, err)
	}

	// Test: Limit supérieur à 100 devrait être limité à 100
	params := TrackListParams{
		Page:      1,
		Limit:     200,
		SortBy:    "created_at",
		SortOrder: "desc",
	}
	tracks, total, err := service.ListTracks(ctx, params)

	assert.NoError(t, err)
	assert.Equal(t, int64(150), total)
	assert.LessOrEqual(t, len(tracks), 100) // Maximum 100
}

func TestTrackService_GetTrackByID_Success(t *testing.T) {
	service, db, cleanup := setupTestTrackServiceForList(t)
	defer cleanup()

	ctx := context.Background()

	// Créer un track
	track := &models.Track{
		UserID:   123,
		Title:    "Test Track",
		FilePath: "/test/track.mp3",
		FileSize: 5 * 1024 * 1024,
		Format:   "MP3",
		Genre:    "Rock",
		Duration: 180,
		IsPublic: true,
		Status:   models.TrackStatusCompleted,
	}
	err := db.Create(track).Error
	assert.NoError(t, err)

	// Récupérer le track
	retrievedTrack, err := service.GetTrackByID(ctx, track.ID)

	assert.NoError(t, err)
	assert.NotNil(t, retrievedTrack)
	assert.Equal(t, track.ID, retrievedTrack.ID)
	assert.Equal(t, track.Title, retrievedTrack.Title)
	assert.Equal(t, track.UserID, retrievedTrack.UserID)
}

func TestTrackService_GetTrackByID_NotFound(t *testing.T) {
	service, _, cleanup := setupTestTrackServiceForList(t)
	defer cleanup()

	ctx := context.Background()

	// Essayer de récupérer un track qui n'existe pas
	_, err := service.GetTrackByID(ctx, 99999)

	assert.Error(t, err)
	assert.Equal(t, ErrTrackNotFound, err)
}

func TestTrackService_UpdateTrack_Success(t *testing.T) {
	service, db, cleanup := setupTestTrackServiceForList(t)
	defer cleanup()

	ctx := context.Background()

	// Créer un track
	track := &models.Track{
		UserID:   123,
		Title:    "Original Title",
		FilePath: "/test/track.mp3",
		FileSize: 5 * 1024 * 1024,
		Format:   "MP3",
		Genre:    "Rock",
		Duration: 180,
		IsPublic: true,
		Status:   models.TrackStatusCompleted,
	}
	err := db.Create(track).Error
	assert.NoError(t, err)

	// Mettre à jour le track
	newTitle := "Updated Title"
	newGenre := "Jazz"
	params := UpdateTrackParams{
		Title: &newTitle,
		Genre: &newGenre,
	}

	updatedTrack, err := service.UpdateTrack(ctx, track.ID, 123, params)

	assert.NoError(t, err)
	assert.NotNil(t, updatedTrack)
	assert.Equal(t, "Updated Title", updatedTrack.Title)
	assert.Equal(t, "Jazz", updatedTrack.Genre)
	assert.Equal(t, track.ID, updatedTrack.ID)
}

func TestTrackService_UpdateTrack_NotFound(t *testing.T) {
	service, _, cleanup := setupTestTrackServiceForList(t)
	defer cleanup()

	ctx := context.Background()

	newTitle := "Updated Title"
	params := UpdateTrackParams{
		Title: &newTitle,
	}

	_, err := service.UpdateTrack(ctx, 99999, 123, params)

	assert.Error(t, err)
	assert.Equal(t, ErrTrackNotFound, err)
}

func TestTrackService_UpdateTrack_Forbidden(t *testing.T) {
	service, db, cleanup := setupTestTrackServiceForList(t)
	defer cleanup()

	ctx := context.Background()

	// Créer un track appartenant à l'utilisateur 123
	track := &models.Track{
		UserID:   123,
		Title:    "Original Title",
		FilePath: "/test/track.mp3",
		FileSize: 5 * 1024 * 1024,
		Format:   "MP3",
		Duration: 180,
		IsPublic: true,
		Status:   models.TrackStatusCompleted,
	}
	err := db.Create(track).Error
	assert.NoError(t, err)

	// Essayer de mettre à jour avec un autre utilisateur
	newTitle := "Updated Title"
	params := UpdateTrackParams{
		Title: &newTitle,
	}

	_, err = service.UpdateTrack(ctx, track.ID, 456, params)

	assert.Error(t, err)
	assert.Equal(t, ErrForbidden, err)
}

func TestTrackService_UpdateTrack_EmptyTitle(t *testing.T) {
	service, db, cleanup := setupTestTrackServiceForList(t)
	defer cleanup()

	ctx := context.Background()

	// Créer un track
	track := &models.Track{
		UserID:   123,
		Title:    "Original Title",
		FilePath: "/test/track.mp3",
		FileSize: 5 * 1024 * 1024,
		Format:   "MP3",
		Duration: 180,
		IsPublic: true,
		Status:   models.TrackStatusCompleted,
	}
	err := db.Create(track).Error
	assert.NoError(t, err)

	// Essayer de mettre à jour avec un titre vide
	emptyTitle := ""
	params := UpdateTrackParams{
		Title: &emptyTitle,
	}

	_, err = service.UpdateTrack(ctx, track.ID, 123, params)

	assert.Error(t, err)
	assert.Contains(t, err.Error(), "title cannot be empty")
}

func TestTrackService_UpdateTrack_NegativeYear(t *testing.T) {
	service, db, cleanup := setupTestTrackServiceForList(t)
	defer cleanup()

	ctx := context.Background()

	// Créer un track
	track := &models.Track{
		UserID:   123,
		Title:    "Original Title",
		FilePath: "/test/track.mp3",
		FileSize: 5 * 1024 * 1024,
		Format:   "MP3",
		Duration: 180,
		IsPublic: true,
		Status:   models.TrackStatusCompleted,
	}
	err := db.Create(track).Error
	assert.NoError(t, err)

	// Essayer de mettre à jour avec une année négative
	negativeYear := -1
	params := UpdateTrackParams{
		Year: &negativeYear,
	}

	_, err = service.UpdateTrack(ctx, track.ID, 123, params)

	assert.Error(t, err)
	assert.Contains(t, err.Error(), "year cannot be negative")
}

func TestTrackService_UpdateTrack_NoUpdates(t *testing.T) {
	service, db, cleanup := setupTestTrackServiceForList(t)
	defer cleanup()

	ctx := context.Background()

	// Créer un track
	track := &models.Track{
		UserID:   123,
		Title:    "Original Title",
		FilePath: "/test/track.mp3",
		FileSize: 5 * 1024 * 1024,
		Format:   "MP3",
		Duration: 180,
		IsPublic: true,
		Status:   models.TrackStatusCompleted,
	}
	err := db.Create(track).Error
	assert.NoError(t, err)

	// Mettre à jour sans aucun paramètre
	params := UpdateTrackParams{}

	updatedTrack, err := service.UpdateTrack(ctx, track.ID, 123, params)

	assert.NoError(t, err)
	assert.NotNil(t, updatedTrack)
	assert.Equal(t, track.Title, updatedTrack.Title)
}

func TestTrackService_DeleteTrack_Success(t *testing.T) {
	service, db, cleanup := setupTestTrackServiceForList(t)
	defer cleanup()

	ctx := context.Background()

	// Créer un track
	track := &models.Track{
		UserID:   123,
		Title:    "Test Track",
		FilePath: "/test/track.mp3",
		FileSize: 5 * 1024 * 1024,
		Format:   "MP3",
		Duration: 180,
		IsPublic: true,
		Status:   models.TrackStatusCompleted,
	}
	err := db.Create(track).Error
	assert.NoError(t, err)

	// Supprimer le track
	err = service.DeleteTrack(ctx, track.ID, 123)
	assert.NoError(t, err)

	// Vérifier que le track a été supprimé
	var deletedTrack models.Track
	err = db.First(&deletedTrack, track.ID).Error
	assert.Error(t, err)
	assert.Equal(t, gorm.ErrRecordNotFound, err)
}

func TestTrackService_DeleteTrack_NotFound(t *testing.T) {
	service, _, cleanup := setupTestTrackServiceForList(t)
	defer cleanup()

	ctx := context.Background()

	// Essayer de supprimer un track qui n'existe pas
	err := service.DeleteTrack(ctx, 99999, 123)

	assert.Error(t, err)
	assert.Equal(t, ErrTrackNotFound, err)
}

func TestTrackService_DeleteTrack_Forbidden(t *testing.T) {
	service, db, cleanup := setupTestTrackServiceForList(t)
	defer cleanup()

	ctx := context.Background()

	// Créer un track appartenant à l'utilisateur 123
	track := &models.Track{
		UserID:   123,
		Title:    "Test Track",
		FilePath: "/test/track.mp3",
		FileSize: 5 * 1024 * 1024,
		Format:   "MP3",
		Duration: 180,
		IsPublic: true,
		Status:   models.TrackStatusCompleted,
	}
	err := db.Create(track).Error
	assert.NoError(t, err)

	// Essayer de supprimer avec un autre utilisateur
	err = service.DeleteTrack(ctx, track.ID, 456)

	assert.Error(t, err)
	assert.Equal(t, ErrForbidden, err)

	// Vérifier que le track n'a pas été supprimé
	var existingTrack models.Track
	err = db.First(&existingTrack, track.ID).Error
	assert.NoError(t, err)
	assert.Equal(t, track.ID, existingTrack.ID)
}

