package services

import (
	"context"
	"os"
	"path/filepath"
	"testing"

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

func setupTestBatchDeleteDB(t *testing.T) (*TrackService, *gorm.DB, string, 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.User{}, &models.Track{})
	require.NoError(t, err)

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

	// Create another user
	user2 := &models.User{
		ID:       2,
		Username: "otheruser",
		Email:    "other@example.com",
		IsActive: true,
	}
	err = db.Create(user2).Error
	require.NoError(t, err)

	// Create temporary directory for test files
	testDir := filepath.Join(os.TempDir(), "test_batch_delete")
	err = os.MkdirAll(testDir, 0755)
	require.NoError(t, err)

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

	// Create service
	service := NewTrackService(db, logger, testDir)

	// Cleanup function
	cleanup := func() {
		os.RemoveAll(testDir)
	}

	return service, db, testDir, cleanup
}

func TestTrackService_BatchDeleteTracks_Success(t *testing.T) {
	service, db, testDir, cleanup := setupTestBatchDeleteDB(t)
	defer cleanup()

	ctx := context.Background()

	// Create test files
	file1 := filepath.Join(testDir, "track1.mp3")
	file2 := filepath.Join(testDir, "track2.mp3")
	os.WriteFile(file1, []byte("test content 1"), 0644)
	os.WriteFile(file2, []byte("test content 2"), 0644)

	// Create tracks
	track1 := &models.Track{
		UserID:   1,
		Title:    "Track 1",
		FilePath: file1,
		FileSize: 1024,
		Format:   "MP3",
		Duration: 180,
		IsPublic: true,
		Status:   models.TrackStatusCompleted,
	}
	track2 := &models.Track{
		UserID:   1,
		Title:    "Track 2",
		FilePath: file2,
		FileSize: 2048,
		Format:   "MP3",
		Duration: 240,
		IsPublic: true,
		Status:   models.TrackStatusCompleted,
	}
	err := db.Create(track1).Error
	require.NoError(t, err)
	err = db.Create(track2).Error
	require.NoError(t, err)

	// Batch delete
	result, err := service.BatchDeleteTracks(ctx, []int64{track1.ID, track2.ID}, 1)
	require.NoError(t, err)
	require.NotNil(t, result)

	// Verify results
	assert.Equal(t, 2, len(result.Deleted))
	assert.Contains(t, result.Deleted, track1.ID)
	assert.Contains(t, result.Deleted, track2.ID)
	assert.Equal(t, 0, len(result.Failed))

	// Verify tracks are deleted from database
	var count int64
	db.Model(&models.Track{}).Where("id IN ?", []int64{track1.ID, track2.ID}).Count(&count)
	assert.Equal(t, int64(0), count)

	// Verify files are deleted
	assert.NoFileExists(t, file1)
	assert.NoFileExists(t, file2)
}

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

	ctx := context.Background()

	result, err := service.BatchDeleteTracks(ctx, []int64{}, 1)
	require.NoError(t, err)
	require.NotNil(t, result)
	assert.Equal(t, 0, len(result.Deleted))
	assert.Equal(t, 0, len(result.Failed))
}

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

	ctx := context.Background()

	// Create a list with more than 100 tracks
	trackIDs := make([]int64, 101)
	for i := range trackIDs {
		trackIDs[i] = int64(i + 1)
	}

	result, err := service.BatchDeleteTracks(ctx, trackIDs, 1)
	assert.Error(t, err)
	assert.Nil(t, result)
	assert.Contains(t, err.Error(), "batch size exceeds maximum")
}

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

	ctx := context.Background()

	result, err := service.BatchDeleteTracks(ctx, []int64{999, 1000}, 1)
	require.NoError(t, err)
	require.NotNil(t, result)

	assert.Equal(t, 0, len(result.Deleted))
	assert.Equal(t, 2, len(result.Failed))
	assert.Equal(t, int64(999), result.Failed[0].TrackID)
	assert.Equal(t, "track not found", result.Failed[0].Error)
}

func TestTrackService_BatchDeleteTracks_Forbidden(t *testing.T) {
	service, db, testDir, cleanup := setupTestBatchDeleteDB(t)
	defer cleanup()

	ctx := context.Background()

	// Create file
	file1 := filepath.Join(testDir, "track1.mp3")
	os.WriteFile(file1, []byte("test content"), 0644)

	// Create track owned by user 1
	track1 := &models.Track{
		UserID:   1,
		Title:    "Track 1",
		FilePath: file1,
		FileSize: 1024,
		Format:   "MP3",
		Duration: 180,
		IsPublic: true,
		Status:   models.TrackStatusCompleted,
	}
	err := db.Create(track1).Error
	require.NoError(t, err)

	// Try to delete as user 2
	result, err := service.BatchDeleteTracks(ctx, []int64{track1.ID}, 2)
	require.NoError(t, err)
	require.NotNil(t, result)

	assert.Equal(t, 0, len(result.Deleted))
	assert.Equal(t, 1, len(result.Failed))
	assert.Equal(t, track1.ID, result.Failed[0].TrackID)
	assert.Contains(t, result.Failed[0].Error, "forbidden")

	// Verify track still exists
	var count int64
	db.Model(&models.Track{}).Where("id = ?", track1.ID).Count(&count)
	assert.Equal(t, int64(1), count)
}

func TestTrackService_BatchDeleteTracks_PartialSuccess(t *testing.T) {
	service, db, testDir, cleanup := setupTestBatchDeleteDB(t)
	defer cleanup()

	ctx := context.Background()

	// Create files
	file1 := filepath.Join(testDir, "track1.mp3")
	file2 := filepath.Join(testDir, "track2.mp3")
	os.WriteFile(file1, []byte("test content 1"), 0644)
	os.WriteFile(file2, []byte("test content 2"), 0644)

	// Create tracks
	track1 := &models.Track{
		UserID:   1,
		Title:    "Track 1",
		FilePath: file1,
		FileSize: 1024,
		Format:   "MP3",
		Duration: 180,
		IsPublic: true,
		Status:   models.TrackStatusCompleted,
	}
	track2 := &models.Track{
		UserID:   2, // Owned by different user
		Title:    "Track 2",
		FilePath: file2,
		FileSize: 2048,
		Format:   "MP3",
		Duration: 240,
		IsPublic: true,
		Status:   models.TrackStatusCompleted,
	}
	err := db.Create(track1).Error
	require.NoError(t, err)
	err = db.Create(track2).Error
	require.NoError(t, err)

	// Try to delete both as user 1
	result, err := service.BatchDeleteTracks(ctx, []int64{track1.ID, track2.ID}, 1)
	require.NoError(t, err)
	require.NotNil(t, result)

	// Track1 should be deleted, track2 should fail
	assert.Equal(t, 1, len(result.Deleted))
	assert.Contains(t, result.Deleted, track1.ID)
	assert.Equal(t, 1, len(result.Failed))
	assert.Equal(t, track2.ID, result.Failed[0].TrackID)
	assert.Contains(t, result.Failed[0].Error, "forbidden")

	// Verify track1 is deleted, track2 still exists
	var count1, count2 int64
	db.Model(&models.Track{}).Where("id = ?", track1.ID).Count(&count1)
	db.Model(&models.Track{}).Where("id = ?", track2.ID).Count(&count2)
	assert.Equal(t, int64(0), count1)
	assert.Equal(t, int64(1), count2)
}

func TestTrackService_deleteTrackFiles(t *testing.T) {
	service, db, testDir, cleanup := setupTestBatchDeleteDB(t)
	defer cleanup()

	ctx := context.Background()

	// Create test files
	file1 := filepath.Join(testDir, "track1.mp3")
	waveform1 := filepath.Join(testDir, "waveform1.png")
	cover1 := filepath.Join(testDir, "cover1.jpg")
	os.WriteFile(file1, []byte("test content"), 0644)
	os.WriteFile(waveform1, []byte("waveform"), 0644)
	os.WriteFile(cover1, []byte("cover"), 0644)

	// Create track with all file paths
	track := &models.Track{
		UserID:       1,
		Title:        "Track with all files",
		FilePath:     file1,
		WaveformPath: waveform1,
		CoverArtPath: cover1,
		FileSize:     1024,
		Format:       "MP3",
		Duration:     180,
		IsPublic:     true,
		Status:       models.TrackStatusCompleted,
	}
	err := db.Create(track).Error
	require.NoError(t, err)

	// Delete files
	err = service.deleteTrackFiles(ctx, track)
	require.NoError(t, err)

	// Verify all files are deleted
	assert.NoFileExists(t, file1)
	assert.NoFileExists(t, waveform1)
	assert.NoFileExists(t, cover1)
}

