package track

import (
	"bytes"
	"context"
	"mime/multipart"
	"os"
	"path/filepath"
	"testing"
	"time"

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

	"veza-backend-api/internal/models"
)

// createTestFileHeader crée un multipart.FileHeader pour les tests
func createTestFileHeader(t *testing.T, content []byte, filename string) *multipart.FileHeader {
	body := &bytes.Buffer{}
	writer := multipart.NewWriter(body)
	part, err := writer.CreateFormFile("file", filename)
	require.NoError(t, err)
	_, err = part.Write(content)
	require.NoError(t, err)
	writer.Close()

	reader := multipart.NewReader(body, writer.Boundary())
	form, err := reader.ReadForm(10 << 20) // 10MB max
	require.NoError(t, err)
	defer form.RemoveAll()

	files := form.File["file"]
	require.Len(t, files, 1)
	return files[0]
}

func TestUploadTrack_Async_Success(t *testing.T) {
	// Setup
	db, err := gorm.Open(sqlite.Open(":memory:"), &gorm.Config{})
	require.NoError(t, err)
	// Migrer les modèles nécessaires
	require.NoError(t, db.AutoMigrate(&models.User{}))
	require.NoError(t, db.AutoMigrate(&models.Track{}))

	logger := zaptest.NewLogger(t)
	uploadDir := t.TempDir()

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

	service := NewTrackService(db, logger, uploadDir)

	// Créer un fichier de test avec magic number MP3 valide (ID3v2)
	// MP3 ID3v2 header: "ID3" + version + flags + size
	testContent := []byte("ID3\x03\x00\x00\x00\x00\x00\x00fake mp3 content for testing")
	fileHeader := createTestFileHeader(t, testContent, "test_audio.mp3")

	// Upload (devrait retourner immédiatement avec Status=Uploading)
	ctx := context.Background()
	track, err := service.UploadTrack(ctx, userID, fileHeader, TrackMetadata{})
	require.NoError(t, err)
	assert.NotNil(t, track)
	assert.Equal(t, models.TrackStatusUploading, track.Status)
	assert.Equal(t, "Upload started", track.StatusMessage)

	// Attendre que la copie se termine (max 5 secondes)
	timeout := time.After(5 * time.Second)
	ticker := time.NewTicker(100 * time.Millisecond)
	defer ticker.Stop()

	for {
		select {
		case <-timeout:
			t.Fatal("Timeout waiting for async copy to complete")
		case <-ticker.C:
			var updatedTrack models.Track
			if err := db.First(&updatedTrack, "id = ?", track.ID).Error; err == nil {
				if updatedTrack.Status != models.TrackStatusUploading {
					// Copie terminée
					assert.Equal(t, models.TrackStatusProcessing, updatedTrack.Status)
					assert.Contains(t, updatedTrack.StatusMessage, "File uploaded")

					// Vérifier que le fichier existe
					filePath := filepath.Join(uploadDir, filepath.Base(track.FilePath))
					_, err := os.Stat(filePath)
					assert.NoError(t, err, "File should exist after successful copy")

					// Vérifier le contenu
					fileContent, err := os.ReadFile(filePath)
					require.NoError(t, err)
					assert.Equal(t, testContent, fileContent)

					return // Test réussi
				}
			}
		}
	}
}

func TestUploadTrack_Async_Interruption(t *testing.T) {
	// Setup
	db, err := gorm.Open(sqlite.Open(":memory:"), &gorm.Config{})
	require.NoError(t, err)
	// Migrer les modèles nécessaires
	require.NoError(t, db.AutoMigrate(&models.User{}))
	require.NoError(t, db.AutoMigrate(&models.Track{}))

	logger := zaptest.NewLogger(t)
	uploadDir := t.TempDir()

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

	service := NewTrackService(db, logger, uploadDir)

	// Créer un fichier de test normal (pas besoin d'un gros fichier pour tester l'interruption)
	testContent := []byte("ID3\x03\x00\x00\x00\x00\x00\x00test content")
	fileHeader := createTestFileHeader(t, testContent, "test_audio.mp3")

	// Upload (le contexte du handler n'est pas annulé, mais on peut tester l'annulation dans copyFileAsync)
	ctx := context.Background()
	track, err := service.UploadTrack(ctx, userID, fileHeader, TrackMetadata{})
	require.NoError(t, err)
	assert.NotNil(t, track)

	// Note: Le contexte passé à UploadTrack n'est pas utilisé dans copyFileAsync
	// copyFileAsync crée son propre contexte avec timeout
	// Pour tester l'interruption, on devrait plutôt tester copyFileAsync directement
	// ou attendre que le fichier soit copié (test de succès)
	// Ce test vérifie plutôt que l'upload fonctionne même si le contexte original est annulé
	time.Sleep(200 * time.Millisecond)

	// Vérifier que le Status est Processing ou Completed (pas Failed)
	var updatedTrack models.Track
	require.NoError(t, db.First(&updatedTrack, "id = ?", track.ID).Error)
	assert.True(t, updatedTrack.Status == models.TrackStatusProcessing || updatedTrack.Status == models.TrackStatusCompleted,
		"Status should be Processing or Completed, got %v", updatedTrack.Status)
}

func TestUploadTrack_Async_ErrorHandling(t *testing.T) {
	// Setup
	db, err := gorm.Open(sqlite.Open(":memory:"), &gorm.Config{})
	require.NoError(t, err)
	// Migrer les modèles nécessaires
	require.NoError(t, db.AutoMigrate(&models.User{}))
	require.NoError(t, db.AutoMigrate(&models.Track{}))

	logger := zaptest.NewLogger(t)
	uploadDir := t.TempDir()

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

	service := NewTrackService(db, logger, uploadDir)

	// Créer un FileHeader valide
	testContent := []byte("ID3\x03\x00\x00\x00\x00\x00\x00test content")
	fileHeader := createTestFileHeader(t, testContent, "test_audio.mp3")

	// Upload (devrait créer le Track et la copie devrait réussir)
	ctx := context.Background()
	track, err := service.UploadTrack(ctx, userID, fileHeader, TrackMetadata{})
	require.NoError(t, err) // La création du Track réussit
	assert.NotNil(t, track)

	// Attendre que la copie se termine
	time.Sleep(500 * time.Millisecond)

	// Vérifier que le Status est Processing (copie réussie)
	var updatedTrack models.Track
	require.NoError(t, db.First(&updatedTrack, "id = ?", track.ID).Error)
	assert.True(t, updatedTrack.Status == models.TrackStatusProcessing || updatedTrack.Status == models.TrackStatusCompleted,
		"Status should be Processing or Completed after successful copy, got %v", updatedTrack.Status)
	assert.NotEqual(t, models.TrackStatusFailed, updatedTrack.Status, "Status should not be Failed for valid file")
}

func TestCopyFileAsync_ContextCancellation(t *testing.T) {
	// Test direct de copyFileAsync
	// Note: Le contexte passé à copyFileAsync n'est pas utilisé (copyFileAsync crée son propre contexte)
	// Ce test vérifie que copyFileAsync fonctionne correctement
	db, err := gorm.Open(sqlite.Open(":memory:"), &gorm.Config{})
	require.NoError(t, err)
	db.AutoMigrate(&models.User{}, &models.Track{})

	logger := zaptest.NewLogger(t)
	uploadDir := t.TempDir()

	service := NewTrackService(db, logger, uploadDir)

	// Créer un Track en DB
	trackID := uuid.New()
	userID := uuid.New()
	fileID := uuid.New()
	track := &models.Track{
		ID:            trackID,
		UserID:        userID,
		FileID:        &fileID, // Required field
		Title:         "Test",
		FilePath:      filepath.Join(uploadDir, "test.mp3"),
		FileSize:      100,
		Format:        "mp3",
		Duration:      0,
		Status:        models.TrackStatusUploading,
		StatusMessage: "Upload started",
	}
	db.Create(track)

	// Créer un contexte (peu importe s'il est annulé, copyFileAsync crée son propre contexte)
	ctx := context.Background()

	// Créer un FileHeader valide avec magic number MP3
	testContent := []byte("ID3\x03\x00\x00\x00\x00\x00\x00test")
	fileHeader := createTestFileHeader(t, testContent, "test_audio.mp3")

	// Appeler copyFileAsync
	service.copyFileAsync(ctx, trackID, fileHeader, track.FilePath, userID)

	// Attendre que la copie se termine
	time.Sleep(300 * time.Millisecond)

	// Vérifier que le Status est Processing (copie réussie)
	var updatedTrack models.Track
	require.NoError(t, db.First(&updatedTrack, "id = ?", trackID).Error)
	assert.Equal(t, models.TrackStatusProcessing, updatedTrack.Status)
	assert.Contains(t, updatedTrack.StatusMessage, "File uploaded")
}
