package services

import (
	"context"
	"github.com/google/uuid"
	"testing"

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

func setupTestTrackUploadService(t *testing.T) (*TrackUploadService, *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{})
	assert.NoError(t, err)

	// Create test service
	logger := zap.NewNop()
	service := NewTrackUploadService(db, logger)

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

	return service, db, cleanup
}

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

	// Create test track
	track := &models.Track{
		UserID:        uuid.New(),
		Title:         "Test Track",
		FilePath:      "/uploads/tracks/test.mp3",
		FileSize:      1024,
		Format:        "MP3",
		Duration:      180,
		Status:        models.TrackStatusProcessing,
		StatusMessage: "Processing audio metadata",
		IsPublic:      true,
	}
	err := db.Create(track).Error
	assert.NoError(t, err)

	// Get progress
	ctx := context.Background()
	progress, err := service.GetUploadProgress(ctx, track.ID)

	// Assert
	assert.NoError(t, err)
	assert.NotNil(t, progress)
	assert.Equal(t, track.ID, progress.TrackID)
	assert.Equal(t, models.TrackStatusProcessing, progress.Status)
	assert.Equal(t, 50, progress.Progress) // Processing = 50%
	assert.Equal(t, "Processing audio metadata", progress.Message)
}

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

	// Get progress for non-existent track
	ctx := context.Background()
	progress, err := service.GetUploadProgress(ctx, uuid.New())

	// Assert
	assert.Error(t, err)
	assert.Nil(t, progress)
	assert.Contains(t, err.Error(), "track not found")
}

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

	statuses := []struct {
		status   models.TrackStatus
		expected int
	}{
		{models.TrackStatusUploading, 25},
		{models.TrackStatusProcessing, 50},
		{models.TrackStatusCompleted, 100},
		{models.TrackStatusFailed, 0},
	}

	ctx := context.Background()

	for i, tt := range statuses {
		// Create test track
		track := &models.Track{
			UserID:   uuid.New(),
			Title:    "Test Track",
			FilePath: "/uploads/tracks/test.mp3",
			FileSize: 1024,
			Format:   "MP3",
			Duration: 180,
			Status:   tt.status,
			IsPublic: true,
		}
		err := db.Create(track).Error
		assert.NoError(t, err)

		// Get progress
		progress, err := service.GetUploadProgress(ctx, track.ID)
		assert.NoError(t, err, "status: %s", tt.status)
		assert.NotNil(t, progress)
		assert.Equal(t, tt.expected, progress.Progress, "status: %s, index: %d", tt.status, i)
		assert.Equal(t, tt.status, progress.Status)
	}
}

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

	// Create test track
	track := &models.Track{
		UserID:   uuid.New(),
		Title:    "Test Track",
		FilePath: "/uploads/tracks/test.mp3",
		FileSize: 1024,
		Format:   "MP3",
		Duration: 180,
		Status:   models.TrackStatusUploading,
		IsPublic: true,
	}
	err := db.Create(track).Error
	assert.NoError(t, err)

	// Update status
	ctx := context.Background()
	err = service.UpdateUploadStatus(ctx, track.ID, models.TrackStatusProcessing, "Processing metadata")
	assert.NoError(t, err)

	// Verify update
	var updatedTrack models.Track
	err = db.First(&updatedTrack, track.ID).Error
	assert.NoError(t, err)
	assert.Equal(t, models.TrackStatusProcessing, updatedTrack.Status)
	assert.Equal(t, "Processing metadata", updatedTrack.StatusMessage)
}

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

	// Create test track with message
	track := &models.Track{
		UserID:        uuid.New(),
		Title:         "Test Track",
		FilePath:      "/uploads/tracks/test.mp3",
		FileSize:      1024,
		Format:        "MP3",
		Duration:      180,
		Status:        models.TrackStatusProcessing,
		StatusMessage: "Previous message",
		IsPublic:      true,
	}
	err := db.Create(track).Error
	assert.NoError(t, err)

	// Update status without message (should preserve existing message)
	ctx := context.Background()
	err = service.UpdateUploadStatus(ctx, track.ID, models.TrackStatusCompleted, "")
	assert.NoError(t, err)

	// Verify update
	var updatedTrack models.Track
	err = db.First(&updatedTrack, track.ID).Error
	assert.NoError(t, err)
	assert.Equal(t, models.TrackStatusCompleted, updatedTrack.Status)
	// Message should be preserved or cleared depending on implementation
}

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

	// Create test track
	track := &models.Track{
		UserID:   uuid.New(),
		Title:    "Test Track",
		FilePath: "/uploads/tracks/test.mp3",
		FileSize: 1024,
		Format:   "MP3",
		Duration: 180,
		Status:   models.TrackStatusUploading,
		IsPublic: true,
	}
	err := db.Create(track).Error
	assert.NoError(t, err)

	// Update status with message
	ctx := context.Background()
	err = service.UpdateUploadStatus(ctx, track.ID, models.TrackStatusFailed, "Upload failed: connection timeout")
	assert.NoError(t, err)

	// Verify update
	var updatedTrack models.Track
	err = db.First(&updatedTrack, track.ID).Error
	assert.NoError(t, err)
	assert.Equal(t, models.TrackStatusFailed, updatedTrack.Status)
	assert.Equal(t, "Upload failed: connection timeout", updatedTrack.StatusMessage)
}

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

	tests := []struct {
		status   models.TrackStatus
		expected int
	}{
		{models.TrackStatusUploading, 25},
		{models.TrackStatusProcessing, 50},
		{models.TrackStatusCompleted, 100},
		{models.TrackStatusFailed, 0},
	}

	for _, tt := range tests {
		t.Run(string(tt.status), func(t *testing.T) {
			progress := service.calculateProgress(tt.status)
			assert.Equal(t, tt.expected, progress)
		})
	}
}

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

	statuses := []models.TrackStatus{
		models.TrackStatusUploading,
		models.TrackStatusProcessing,
		models.TrackStatusCompleted,
		models.TrackStatusFailed,
	}

	ctx := context.Background()

	for _, status := range statuses {
		// Create test track
		track := &models.Track{
			UserID:   uuid.New(),
			Title:    "Test Track",
			FilePath: "/uploads/tracks/test.mp3",
			FileSize: 1024,
			Format:   "MP3",
			Duration: 180,
			Status:   models.TrackStatusUploading,
			IsPublic: true,
		}
		err := db.Create(track).Error
		assert.NoError(t, err)

		// Update status
		err = service.UpdateUploadStatus(ctx, track.ID, status, "Status updated")
		assert.NoError(t, err, "Failed to update status: %s", status)

		// Verify
		var updatedTrack models.Track
		err = db.First(&updatedTrack, track.ID).Error
		assert.NoError(t, err)
		assert.Equal(t, status, updatedTrack.Status)
	}
}
