package services

import (
	"context"
	"fmt"
	"testing"
	"time"

	"github.com/google/uuid"

	"veza-backend-api/internal/models"

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

func setupTestTrackSearchService(t *testing.T) (*TrackSearchService, *gorm.DB, uuid.UUID, func()) {
	// Setup in-memory SQLite database
	db, err := gorm.Open(sqlite.Open(":memory:"), &gorm.Config{})
	require.NoError(t, err)

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

	// Create test user
	userID := uuid.New()
	user := &models.User{
		ID:       userID,
		Username: "testuser",
		Email:    "test@example.com",
		IsActive: true,
	}
	err = db.Create(user).Error
	require.NoError(t, err)

	// Setup service
	service := NewTrackSearchService(db)

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

	return service, db, userID, cleanup
}

func TestTrackSearchService_SearchTracks_FullTextSearch(t *testing.T) {
	service, db, userID, cleanup := setupTestTrackSearchService(t)
	defer cleanup()

	ctx := context.Background()

	// Create test tracks
	track1 := &models.Track{
		UserID:   userID,
		Title:    "Test Track 1",
		Artist:   "Artist One",
		FilePath: "/test/track1.mp3",
		FileSize: 5 * 1024 * 1024,
		Format:   "MP3",
		Duration: 180,
		Genre:    "Rock",
		IsPublic: true,
		Status:   models.TrackStatusCompleted,
	}
	err := db.Create(track1).Error
	require.NoError(t, err)

	track2 := &models.Track{
		UserID:   userID,
		Title:    "Another Track",
		Artist:   "Artist Two",
		FilePath: "/test/track2.mp3",
		FileSize: 6 * 1024 * 1024,
		Format:   "FLAC",
		Duration: 200,
		Genre:    "Pop",
		IsPublic: true,
		Status:   models.TrackStatusCompleted,
	}
	err = db.Create(track2).Error
	require.NoError(t, err)

	// Test full-text search
	results, total, err := service.SearchTracks(ctx, TrackSearchParams{
		Query: "Test",
		Page:  1,
		Limit: 10,
	})

	assert.NoError(t, err)
	assert.Equal(t, int64(1), total)
	assert.Len(t, results, 1)
	assert.Equal(t, "Test Track 1", results[0].Title)
}

func TestTrackSearchService_SearchTracks_GenreFilter(t *testing.T) {
	service, db, userID, cleanup := setupTestTrackSearchService(t)
	defer cleanup()

	ctx := context.Background()

	// Create test tracks
	track1 := &models.Track{
		UserID:   userID,
		Title:    "Rock Track",
		Artist:   "Rock Artist",
		FilePath: "/test/track1.mp3",
		FileSize: 5 * 1024 * 1024,
		Format:   "MP3",
		Duration: 180,
		Genre:    "Rock",
		IsPublic: true,
		Status:   models.TrackStatusCompleted,
	}
	err := db.Create(track1).Error
	require.NoError(t, err)

	track2 := &models.Track{
		UserID:   userID,
		Title:    "Pop Track",
		Artist:   "Pop Artist",
		FilePath: "/test/track2.mp3",
		FileSize: 6 * 1024 * 1024,
		Format:   "FLAC",
		Duration: 200,
		Genre:    "Pop",
		IsPublic: true,
		Status:   models.TrackStatusCompleted,
	}
	err = db.Create(track2).Error
	require.NoError(t, err)

	// Test genre filter
	genre := "Rock"
	results, total, err := service.SearchTracks(ctx, TrackSearchParams{
		Genre: &genre,
		Page:  1,
		Limit: 10,
	})

	assert.NoError(t, err)
	assert.Equal(t, int64(1), total)
	assert.Len(t, results, 1)
	assert.Equal(t, "Rock Track", results[0].Title)
}

func TestTrackSearchService_SearchTracks_DurationFilter(t *testing.T) {
	service, db, userID, cleanup := setupTestTrackSearchService(t)
	defer cleanup()

	ctx := context.Background()

	// Create test tracks
	track1 := &models.Track{
		UserID:   userID,
		Title:    "Short Track",
		Artist:   "Artist One",
		FilePath: "/test/track1.mp3",
		FileSize: 5 * 1024 * 1024,
		Format:   "MP3",
		Duration: 120, // 2 minutes
		Genre:    "Rock",
		IsPublic: true,
		Status:   models.TrackStatusCompleted,
	}
	err := db.Create(track1).Error
	require.NoError(t, err)

	track2 := &models.Track{
		UserID:   userID,
		Title:    "Long Track",
		Artist:   "Artist Two",
		FilePath: "/test/track2.mp3",
		FileSize: 6 * 1024 * 1024,
		Format:   "FLAC",
		Duration: 300, // 5 minutes
		Genre:    "Pop",
		IsPublic: true,
		Status:   models.TrackStatusCompleted,
	}
	err = db.Create(track2).Error
	require.NoError(t, err)

	// Test min duration filter
	minDuration := 200
	results, total, err := service.SearchTracks(ctx, TrackSearchParams{
		MinDuration: &minDuration,
		Page:        1,
		Limit:       10,
	})

	assert.NoError(t, err)
	assert.Equal(t, int64(1), total)
	assert.Len(t, results, 1)
	assert.Equal(t, "Long Track", results[0].Title)

	// Test max duration filter
	maxDuration := 150
	results, total, err = service.SearchTracks(ctx, TrackSearchParams{
		MaxDuration: &maxDuration,
		Page:        1,
		Limit:       10,
	})

	assert.NoError(t, err)
	assert.Equal(t, int64(1), total)
	assert.Len(t, results, 1)
	assert.Equal(t, "Short Track", results[0].Title)
}

func TestTrackSearchService_SearchTracks_FormatFilter(t *testing.T) {
	service, db, userID, cleanup := setupTestTrackSearchService(t)
	defer cleanup()

	ctx := context.Background()

	// Create test tracks
	track1 := &models.Track{
		UserID:   userID,
		Title:    "MP3 Track",
		Artist:   "Artist One",
		FilePath: "/test/track1.mp3",
		FileSize: 5 * 1024 * 1024,
		Format:   "MP3",
		Duration: 180,
		Genre:    "Rock",
		IsPublic: true,
		Status:   models.TrackStatusCompleted,
	}
	err := db.Create(track1).Error
	require.NoError(t, err)

	track2 := &models.Track{
		UserID:   userID,
		Title:    "FLAC Track",
		Artist:   "Artist Two",
		FilePath: "/test/track2.flac",
		FileSize: 6 * 1024 * 1024,
		Format:   "FLAC",
		Duration: 200,
		Genre:    "Pop",
		IsPublic: true,
		Status:   models.TrackStatusCompleted,
	}
	err = db.Create(track2).Error
	require.NoError(t, err)

	// Test format filter
	format := "MP3"
	results, total, err := service.SearchTracks(ctx, TrackSearchParams{
		Format: &format,
		Page:   1,
		Limit:  10,
	})

	assert.NoError(t, err)
	assert.Equal(t, int64(1), total)
	assert.Len(t, results, 1)
	assert.Equal(t, "MP3 Track", results[0].Title)
}

func TestTrackSearchService_SearchTracks_DateRangeFilter(t *testing.T) {
	service, db, userID, cleanup := setupTestTrackSearchService(t)
	defer cleanup()

	ctx := context.Background()

	// Create test tracks with different dates
	now := time.Now()
	oldDate := now.AddDate(0, -2, 0)    // 2 months ago
	recentDate := now.AddDate(0, 0, -5) // 5 days ago

	track1 := &models.Track{
		UserID:    userID,
		Title:     "Old Track",
		Artist:    "Artist One",
		FilePath:  "/test/track1.mp3",
		FileSize:  5 * 1024 * 1024,
		Format:    "MP3",
		Duration:  180,
		Genre:     "Rock",
		IsPublic:  true,
		Status:    models.TrackStatusCompleted,
		CreatedAt: oldDate,
	}
	err := db.Create(track1).Error
	require.NoError(t, err)

	track2 := &models.Track{
		UserID:    userID,
		Title:     "Recent Track",
		Artist:    "Artist Two",
		FilePath:  "/test/track2.mp3",
		FileSize:  6 * 1024 * 1024,
		Format:    "FLAC",
		Duration:  200,
		Genre:     "Pop",
		IsPublic:  true,
		Status:    models.TrackStatusCompleted,
		CreatedAt: recentDate,
	}
	err = db.Create(track2).Error
	require.NoError(t, err)

	// Test min date filter
	minDate := now.AddDate(0, -1, 0).Format(time.RFC3339) // 1 month ago
	results, total, err := service.SearchTracks(ctx, TrackSearchParams{
		MinDate: &minDate,
		Page:    1,
		Limit:   10,
	})

	assert.NoError(t, err)
	assert.Equal(t, int64(1), total)
	assert.Len(t, results, 1)
	assert.Equal(t, "Recent Track", results[0].Title)
}

func TestTrackSearchService_SearchTracks_Pagination(t *testing.T) {
	service, db, userID, cleanup := setupTestTrackSearchService(t)
	defer cleanup()

	ctx := context.Background()

	// Create multiple test tracks
	for i := 0; i < 25; i++ {
		track := &models.Track{
			UserID:   userID,
			Title:    "Track " + fmt.Sprintf("%d", i+1),
			Artist:   "Artist",
			FilePath: fmt.Sprintf("/test/track%d.mp3", i+1),
			FileSize: 5 * 1024 * 1024,
			Format:   "MP3",
			Duration: 180,
			Genre:    "Rock",
			IsPublic: true,
			Status:   models.TrackStatusCompleted,
		}
		err := db.Create(track).Error
		require.NoError(t, err)
	}

	// Test pagination - first page
	results, total, err := service.SearchTracks(ctx, TrackSearchParams{
		Page:  1,
		Limit: 10,
	})

	assert.NoError(t, err)
	assert.Equal(t, int64(25), total)
	assert.Len(t, results, 10)

	// Test pagination - second page
	results, total, err = service.SearchTracks(ctx, TrackSearchParams{
		Page:  2,
		Limit: 10,
	})

	assert.NoError(t, err)
	assert.Equal(t, int64(25), total)
	assert.Len(t, results, 10)

	// Test pagination - third page
	results, total, err = service.SearchTracks(ctx, TrackSearchParams{
		Page:  3,
		Limit: 10,
	})

	assert.NoError(t, err)
	assert.Equal(t, int64(25), total)
	assert.Len(t, results, 5) // Only 5 remaining
}

func TestTrackSearchService_SearchTracks_Sorting(t *testing.T) {
	service, db, userID, cleanup := setupTestTrackSearchService(t)
	defer cleanup()

	ctx := context.Background()

	// Create test tracks
	track1 := &models.Track{
		UserID:   userID,
		Title:    "A Track",
		Artist:   "Artist One",
		FilePath: "/test/track1.mp3",
		FileSize: 5 * 1024 * 1024,
		Format:   "MP3",
		Duration: 180,
		Genre:    "Rock",
		IsPublic: true,
		Status:   models.TrackStatusCompleted,
	}
	err := db.Create(track1).Error
	require.NoError(t, err)

	track2 := &models.Track{
		UserID:   userID,
		Title:    "Z Track",
		Artist:   "Artist Two",
		FilePath: "/test/track2.mp3",
		FileSize: 6 * 1024 * 1024,
		Format:   "FLAC",
		Duration: 200,
		Genre:    "Pop",
		IsPublic: true,
		Status:   models.TrackStatusCompleted,
	}
	err = db.Create(track2).Error
	require.NoError(t, err)

	// Test sorting by title ascending
	results, total, err := service.SearchTracks(ctx, TrackSearchParams{
		SortBy:    "title",
		SortOrder: "asc",
		Page:      1,
		Limit:     10,
	})

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

func TestTrackSearchService_SearchTracks_OnlyPublic(t *testing.T) {
	service, db, userID, cleanup := setupTestTrackSearchService(t)
	defer cleanup()

	ctx := context.Background()

	// Create public track
	track1 := &models.Track{
		UserID:   userID,
		Title:    "Public Track",
		Artist:   "Artist One",
		FilePath: "/test/track1.mp3",
		FileSize: 5 * 1024 * 1024,
		Format:   "MP3",
		Duration: 180,
		Genre:    "Rock",
		IsPublic: true,
		Status:   models.TrackStatusCompleted,
	}
	err := db.Create(track1).Error
	require.NoError(t, err)

	// Create private track
	track2 := &models.Track{
		UserID:   userID,
		Title:    "Private Track",
		Artist:   "Artist Two",
		FilePath: "/test/track2.mp3",
		FileSize: 6 * 1024 * 1024,
		Format:   "FLAC",
		Duration: 200,
		Genre:    "Pop",
		IsPublic: false,
		Status:   models.TrackStatusCompleted,
	}
	err = db.Create(track2).Error
	require.NoError(t, err)
	// Force IsPublic to false (GORM might use default value true)
	err = db.Model(track2).Update("is_public", false).Error
	require.NoError(t, err)

	// Test that only public tracks are returned
	results, total, err := service.SearchTracks(ctx, TrackSearchParams{
		Page:  1,
		Limit: 10,
	})

	assert.NoError(t, err)
	assert.Equal(t, int64(1), total)
	assert.Len(t, results, 1)
	assert.Equal(t, "Public Track", results[0].Title)
}

func TestTrackSearchService_SearchTracks_CombinedFilters(t *testing.T) {
	service, db, userID, cleanup := setupTestTrackSearchService(t)
	defer cleanup()

	ctx := context.Background()

	// Create test tracks with different attributes
	track1 := &models.Track{
		UserID:   userID,
		Title:    "Rock MP3 Track",
		Artist:   "Rock Artist",
		FilePath: "/test/track1.mp3",
		FileSize: 5 * 1024 * 1024,
		Format:   "MP3",
		Duration: 180,
		Genre:    "Rock",
		IsPublic: true,
		Status:   models.TrackStatusCompleted,
	}
	err := db.Create(track1).Error
	require.NoError(t, err)

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

	track3 := &models.Track{
		UserID:   userID,
		Title:    "Rock FLAC Track",
		Artist:   "Rock Artist 2",
		FilePath: "/test/track3.flac",
		FileSize: 7 * 1024 * 1024,
		Format:   "FLAC",
		Duration: 250,
		Genre:    "Rock",
		IsPublic: true,
		Status:   models.TrackStatusCompleted,
	}
	err = db.Create(track3).Error
	require.NoError(t, err)

	// Test combined filters: genre + format
	genre := "Rock"
	format := "MP3"
	results, total, err := service.SearchTracks(ctx, TrackSearchParams{
		Genre:  &genre,
		Format: &format,
		Page:   1,
		Limit:  10,
	})

	assert.NoError(t, err)
	assert.Equal(t, int64(1), total)
	assert.Len(t, results, 1)
	assert.Equal(t, "Rock MP3 Track", results[0].Title)

	// Test combined filters: genre + duration range
	minDuration := 200
	maxDuration := 300
	results, total, err = service.SearchTracks(ctx, TrackSearchParams{
		Genre:       &genre,
		MinDuration: &minDuration,
		MaxDuration: &maxDuration,
		Page:        1,
		Limit:       10,
	})

	assert.NoError(t, err)
	assert.Equal(t, int64(1), total)
	assert.Len(t, results, 1)
	assert.Equal(t, "Rock FLAC Track", results[0].Title)
}

func TestTrackSearchService_SearchTracks_SortByPopularity(t *testing.T) {
	service, db, userID, cleanup := setupTestTrackSearchService(t)
	defer cleanup()

	ctx := context.Background()

	// Create test tracks with different like counts
	track1 := &models.Track{
		UserID:    userID,
		Title:     "Low Likes Track",
		Artist:    "Artist One",
		FilePath:  "/test/track1.mp3",
		FileSize:  5 * 1024 * 1024,
		Format:    "MP3",
		Duration:  180,
		Genre:     "Rock",
		IsPublic:  true,
		Status:    models.TrackStatusCompleted,
		LikeCount: 5,
	}
	err := db.Create(track1).Error
	require.NoError(t, err)

	track2 := &models.Track{
		UserID:    userID,
		Title:     "High Likes Track",
		Artist:    "Artist Two",
		FilePath:  "/test/track2.mp3",
		FileSize:  6 * 1024 * 1024,
		Format:    "FLAC",
		Duration:  200,
		Genre:     "Pop",
		IsPublic:  true,
		Status:    models.TrackStatusCompleted,
		LikeCount: 50,
	}
	err = db.Create(track2).Error
	require.NoError(t, err)

	// Test sorting by popularity (descending)
	results, total, err := service.SearchTracks(ctx, TrackSearchParams{
		SortBy:    "popularity",
		SortOrder: "desc",
		Page:      1,
		Limit:     10,
	})

	assert.NoError(t, err)
	assert.Equal(t, int64(2), total)
	assert.Len(t, results, 2)
	assert.Equal(t, "High Likes Track", results[0].Title) // Highest likes first
	assert.Equal(t, "Low Likes Track", results[1].Title)
}

func TestTrackSearchService_SearchTracks_SortByPlayCount(t *testing.T) {
	service, db, userID, cleanup := setupTestTrackSearchService(t)
	defer cleanup()

	ctx := context.Background()

	// Create test tracks with different play counts
	track1 := &models.Track{
		UserID:    userID,
		Title:     "Low Plays Track",
		Artist:    "Artist One",
		FilePath:  "/test/track1.mp3",
		FileSize:  5 * 1024 * 1024,
		Format:    "MP3",
		Duration:  180,
		Genre:     "Rock",
		IsPublic:  true,
		Status:    models.TrackStatusCompleted,
		PlayCount: 10,
	}
	err := db.Create(track1).Error
	require.NoError(t, err)

	track2 := &models.Track{
		UserID:    userID,
		Title:     "High Plays Track",
		Artist:    "Artist Two",
		FilePath:  "/test/track2.mp3",
		FileSize:  6 * 1024 * 1024,
		Format:    "FLAC",
		Duration:  200,
		Genre:     "Pop",
		IsPublic:  true,
		Status:    models.TrackStatusCompleted,
		PlayCount: 100,
	}
	err = db.Create(track2).Error
	require.NoError(t, err)

	// Test sorting by play_count (descending)
	results, total, err := service.SearchTracks(ctx, TrackSearchParams{
		SortBy:    "play_count",
		SortOrder: "desc",
		Page:      1,
		Limit:     10,
	})

	assert.NoError(t, err)
	assert.Equal(t, int64(2), total)
	assert.Len(t, results, 2)
	assert.Equal(t, "High Plays Track", results[0].Title) // Highest plays first
	assert.Equal(t, "Low Plays Track", results[1].Title)
}

func TestTrackSearchService_SearchTracks_SortByTitle(t *testing.T) {
	service, db, userID, cleanup := setupTestTrackSearchService(t)
	defer cleanup()

	ctx := context.Background()

	// Create test tracks with different titles
	track1 := &models.Track{
		UserID:   userID,
		Title:    "Zebra Track",
		Artist:   "Artist One",
		FilePath: "/test/track1.mp3",
		FileSize: 5 * 1024 * 1024,
		Format:   "MP3",
		Duration: 180,
		Genre:    "Rock",
		IsPublic: true,
		Status:   models.TrackStatusCompleted,
	}
	err := db.Create(track1).Error
	require.NoError(t, err)

	track2 := &models.Track{
		UserID:   userID,
		Title:    "Alpha Track",
		Artist:   "Artist Two",
		FilePath: "/test/track2.mp3",
		FileSize: 6 * 1024 * 1024,
		Format:   "FLAC",
		Duration: 200,
		Genre:    "Pop",
		IsPublic: true,
		Status:   models.TrackStatusCompleted,
	}
	err = db.Create(track2).Error
	require.NoError(t, err)

	// Test sorting by title (ascending)
	results, total, err := service.SearchTracks(ctx, TrackSearchParams{
		SortBy:    "title",
		SortOrder: "asc",
		Page:      1,
		Limit:     10,
	})

	assert.NoError(t, err)
	assert.Equal(t, int64(2), total)
	assert.Len(t, results, 2)
	assert.Equal(t, "Alpha Track", results[0].Title) // Alphabetically first
	assert.Equal(t, "Zebra Track", results[1].Title)
}

func TestTrackSearchService_SearchTracks_SortByCommentCount(t *testing.T) {
	service, db, userID, cleanup := setupTestTrackSearchService(t)
	defer cleanup()

	ctx := context.Background()

	// Create test tracks
	track1 := &models.Track{
		UserID:   userID,
		Title:    "Track With Comments",
		Artist:   "Artist One",
		FilePath: "/test/track1.mp3",
		FileSize: 5 * 1024 * 1024,
		Format:   "MP3",
		Duration: 180,
		Genre:    "Rock",
		IsPublic: true,
		Status:   models.TrackStatusCompleted,
	}
	err := db.Create(track1).Error
	require.NoError(t, err)

	track2 := &models.Track{
		UserID:   userID,
		Title:    "Track Without Comments",
		Artist:   "Artist Two",
		FilePath: "/test/track2.mp3",
		FileSize: 6 * 1024 * 1024,
		Format:   "FLAC",
		Duration: 200,
		Genre:    "Pop",
		IsPublic: true,
		Status:   models.TrackStatusCompleted,
	}
	err = db.Create(track2).Error
	require.NoError(t, err)

	// Create comments for track1
	err = db.AutoMigrate(&models.TrackComment{})
	require.NoError(t, err)

	comment1 := &models.TrackComment{
		TrackID: track1.ID,
		UserID:  userID,
		Content: "Great track!",
	}
	err = db.Create(comment1).Error
	require.NoError(t, err)

	comment2 := &models.TrackComment{
		TrackID: track1.ID,
		UserID:  userID,
		Content: "Love it!",
	}
	err = db.Create(comment2).Error
	require.NoError(t, err)

	// Test sorting by comment_count (descending)
	results, total, err := service.SearchTracks(ctx, TrackSearchParams{
		SortBy:    "comment_count",
		SortOrder: "desc",
		Page:      1,
		Limit:     10,
	})

	assert.NoError(t, err)
	assert.Equal(t, int64(2), total)
	assert.Len(t, results, 2)
	assert.Equal(t, "Track With Comments", results[0].Title) // Most comments first
	assert.Equal(t, "Track Without Comments", results[1].Title)
}
