//go:build load
// +build load

package load

import (
	"bytes"
	"context"
	"encoding/json"
	"fmt"
	"io"
	"mime/multipart"
	"net/http"
	"net/http/httptest"
	"os"
	"sync"
	"testing"
	"time"

	"github.com/gin-gonic/gin"
	"github.com/google/uuid"
	"github.com/redis/go-redis/v9"
	"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/core/track"
	"veza-backend-api/internal/models"
	"veza-backend-api/internal/services"
)

// setupLoadTestRouter crée un router de test pour les tests de charge
func setupLoadTestRouter(t *testing.T) (*gin.Engine, *gorm.DB, *redis.Client, func()) {
	gin.SetMode(gin.TestMode)
	logger := zaptest.NewLogger(t)

	// Setup in-memory SQLite database
	db, err := gorm.Open(sqlite.Open(":memory:"), &gorm.Config{})
	require.NoError(t, err)
	db.Exec("PRAGMA foreign_keys = ON")

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

	// Setup Redis (use in-memory or skip if not available)
	redisClient := redis.NewClient(&redis.Options{
		Addr: "localhost:6379",
		DB:   15, // Use DB 15 for tests
	})
	ctx := context.Background()
	_, err = redisClient.Ping(ctx).Result()
	if err != nil {
		t.Logf("Redis not available, skipping Redis-dependent tests: %v", err)
		redisClient = nil
	} else {
		// Cleanup Redis
		redisClient.FlushDB(ctx)
		t.Cleanup(func() {
			if redisClient != nil {
				redisClient.FlushDB(ctx)
				redisClient.Close()
			}
		})
	}

	// Setup services
	uploadDir := t.TempDir()
	chunksDir := uploadDir + "/chunks"
	os.MkdirAll(chunksDir, 0755)

	trackService := track.NewTrackService(db, logger, uploadDir)
	trackUploadService := services.NewTrackUploadService(db, logger)
	chunkService := services.NewTrackChunkService(chunksDir, redisClient, logger)
	likeService := services.NewTrackLikeService(db, logger)
	streamService := services.NewStreamService("http://localhost:8082", logger)
	trackHandler := track.NewTrackHandler(trackService, trackUploadService, chunkService, likeService, streamService)

	// Setup UploadValidator (disable ClamAV for load tests)
	uploadConfig := &services.UploadConfig{
		ClamAVEnabled:      false,
		ClamAVRequired:     false,
		MaxAudioSize:       100 * 1024 * 1024, // 100MB
		AllowedAudioTypes:  []string{"audio/mpeg", "audio/flac", "audio/wav", "audio/ogg"},
	}
	uploadValidator, err := services.NewUploadValidator(uploadConfig, logger)
	require.NoError(t, err)
	trackHandler.SetUploadValidator(uploadValidator)

	// Create router
	router := gin.New()

	// Mock authentication middleware
	router.Use(func(c *gin.Context) {
		userIDStr := c.GetHeader("X-User-ID")
		if userIDStr != "" {
			uid, err := uuid.Parse(userIDStr)
			if err == nil {
				c.Set("user_id", uid)
			}
		}
		c.Next()
	})

	// Upload routes
	tracksGroup := router.Group("/api/v1/tracks")
	{
		tracksGroup.POST("", trackHandler.UploadTrack)
		tracksGroup.POST("/initiate", trackHandler.InitiateChunkedUpload)
		tracksGroup.POST("/chunk", trackHandler.UploadChunk)
		tracksGroup.POST("/complete", trackHandler.CompleteChunkedUpload)
		tracksGroup.GET("/:id/status", trackHandler.GetUploadStatus)
	}

	cleanup := func() {
		// Cleanup handled by t.TempDir() and t.Cleanup
	}

	return router, db, redisClient, cleanup
}

// createTestFile crée un fichier de test
func createTestFile(t *testing.T, size int) *bytes.Buffer {
	data := make([]byte, size)
	for i := range data {
		data[i] = byte(i % 256)
	}
	return bytes.NewBuffer(data)
}

// createMultipartFormData crée un multipart form avec un fichier
func createMultipartFormData(t *testing.T, fieldName, filename string, fileData *bytes.Buffer) ([]byte, string) {
	var buf bytes.Buffer
	writer := multipart.NewWriter(&buf)

	part, err := writer.CreateFormFile(fieldName, filename)
	require.NoError(t, err)
	_, err = io.Copy(part, fileData)
	require.NoError(t, err)

	writer.Close()
	return buf.Bytes(), writer.FormDataContentType()
}

// TestLoad_ConcurrentSimpleUploads teste les uploads simples concurrents
func TestLoad_ConcurrentSimpleUploads(t *testing.T) {
	if testing.Short() {
		t.Skip("Skipping load test in short mode")
	}

	router, db, _, cleanup := setupLoadTestRouter(t)
	defer cleanup()

	// Create test users
	numUsers := 10
	users := make([]uuid.UUID, numUsers)
	for i := 0; i < numUsers; i++ {
		userID := uuid.New()
		users[i] = userID
		user := &models.User{
			ID:       userID,
			Email:    fmt.Sprintf("user%d@example.com", i),
			Username: fmt.Sprintf("user%d", i),
		}
		db.Create(user)
	}

	// Test parameters
	concurrentUploads := 20
	fileSize := 1024 * 100 // 100KB per file

	var wg sync.WaitGroup
	var mu sync.Mutex
	successCount := 0
	errorCount := 0
	var durations []time.Duration

	startTime := time.Now()

	// Launch concurrent uploads
	for i := 0; i < concurrentUploads; i++ {
		wg.Add(1)
		go func(index int) {
			defer wg.Done()

			userID := users[index%numUsers]
			fileData := createTestFile(t, fileSize)
			body, contentType := createMultipartFormData(t, "file", fmt.Sprintf("test%d.mp3", index), fileData)

			req := httptest.NewRequest("POST", "/api/v1/tracks", bytes.NewBuffer(body))
			req.Header.Set("Content-Type", contentType)
			req.Header.Set("X-User-ID", userID.String())
			req.Header.Set("X-Content-Creator-Role", "true") // Mock role
			// Add form fields
			req.ParseMultipartForm(10 << 20)
			req.Form.Set("title", fmt.Sprintf("Test Track %d", index))
			req.Form.Set("artist", "Test Artist")
			req.Form.Set("is_public", "true")

			reqStart := time.Now()
			w := httptest.NewRecorder()
			router.ServeHTTP(w, req)
			duration := time.Since(reqStart)

			mu.Lock()
			if w.Code == http.StatusAccepted || w.Code == http.StatusCreated {
				successCount++
			} else {
				errorCount++
				t.Logf("Upload %d failed with status %d: %s", index, w.Code, w.Body.String())
			}
			durations = append(durations, duration)
			mu.Unlock()
		}(i)
	}

	wg.Wait()
	totalTime := time.Since(startTime)

	// Calculate statistics
	avgDuration := time.Duration(0)
	for _, d := range durations {
		avgDuration += d
	}
	if len(durations) > 0 {
		avgDuration /= time.Duration(len(durations))
	}

	t.Logf("Concurrent Uploads Test Results:")
	t.Logf("  Total uploads: %d", concurrentUploads)
	t.Logf("  Successful: %d", successCount)
	t.Logf("  Failed: %d", errorCount)
	t.Logf("  Total time: %v", totalTime)
	t.Logf("  Average response time: %v", avgDuration)
	t.Logf("  Throughput: %.2f uploads/second", float64(concurrentUploads)/totalTime.Seconds())

	assert.Greater(t, successCount, concurrentUploads*8/10, "At least 80% of uploads should succeed")
	assert.Less(t, avgDuration, 5*time.Second, "Average response time should be less than 5 seconds")
}

// TestLoad_ConcurrentChunkedUploads teste les uploads par chunks concurrents
func TestLoad_ConcurrentChunkedUploads(t *testing.T) {
	if testing.Short() {
		t.Skip("Skipping load test in short mode")
	}

	router, db, redisClient, cleanup := setupLoadTestRouter(t)
	defer cleanup()

	if redisClient == nil {
		t.Skip("Redis not available, skipping chunked upload load test")
	}

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

	// Test parameters
	concurrentUploads := 5
	chunksPerUpload := 10
	chunkSize := 1024 * 50 // 50KB per chunk

	var wg sync.WaitGroup
	var mu sync.Mutex
	successCount := 0
	errorCount := 0
	uploadIDs := make([]string, concurrentUploads)

	startTime := time.Now()

	// Phase 1: Initiate all uploads
	for i := 0; i < concurrentUploads; i++ {
		wg.Add(1)
		go func(index int) {
			defer wg.Done()

			reqBody := map[string]interface{}{
				"total_chunks": chunksPerUpload,
				"total_size":   int64(chunksPerUpload * chunkSize),
				"filename":     fmt.Sprintf("test%d.mp3", index),
			}
			body, _ := json.Marshal(reqBody)

			req := httptest.NewRequest("POST", "/api/v1/tracks/initiate", bytes.NewBuffer(body))
			req.Header.Set("Content-Type", "application/json")
			req.Header.Set("X-User-ID", userID.String())

			w := httptest.NewRecorder()
			router.ServeHTTP(w, req)

			mu.Lock()
			if w.Code == http.StatusOK {
				var response map[string]interface{}
				json.Unmarshal(w.Body.Bytes(), &response)
				if data, ok := response["data"].(map[string]interface{}); ok {
					if uploadID, ok := data["upload_id"].(string); ok {
						uploadIDs[index] = uploadID
						successCount++
					}
				}
			} else {
				errorCount++
			}
			mu.Unlock()
		}(i)
	}

	wg.Wait()

	// Phase 2: Upload chunks concurrently
	var chunkWg sync.WaitGroup
	chunkSuccessCount := 0
	chunkErrorCount := 0

	for uploadIndex, uploadID := range uploadIDs {
		if uploadID == "" {
			continue
		}
		for chunkNum := 1; chunkNum <= chunksPerUpload; chunkNum++ {
			chunkWg.Add(1)
			go func(uploadIdx int, uploadID string, chunkNum int) {
				defer chunkWg.Done()

				chunkData := createTestFile(t, chunkSize)
				body, contentType := createMultipartFormData(t, "chunk", fmt.Sprintf("chunk%d", chunkNum), chunkData)

				req := httptest.NewRequest("POST", "/api/v1/tracks/chunk", bytes.NewBuffer(body))
				req.Header.Set("Content-Type", contentType)
				req.Header.Set("X-User-ID", userID.String())
				req.ParseMultipartForm(10 << 20)
				req.Form.Set("upload_id", uploadID)
				req.Form.Set("chunk_number", fmt.Sprintf("%d", chunkNum))
				req.Form.Set("total_chunks", fmt.Sprintf("%d", chunksPerUpload))
				req.Form.Set("total_size", fmt.Sprintf("%d", chunksPerUpload*chunkSize))
				req.Form.Set("filename", fmt.Sprintf("test%d.mp3", uploadIdx))

				w := httptest.NewRecorder()
				router.ServeHTTP(w, req)

				mu.Lock()
				if w.Code == http.StatusOK {
					chunkSuccessCount++
				} else {
					chunkErrorCount++
				}
				mu.Unlock()
			}(uploadIndex, uploadID, chunkNum)
		}
	}

	chunkWg.Wait()
	totalTime := time.Since(startTime)

	t.Logf("Concurrent Chunked Uploads Test Results:")
	t.Logf("  Total uploads initiated: %d", concurrentUploads)
	t.Logf("  Successful initiations: %d", successCount)
	t.Logf("  Total chunks uploaded: %d", chunkSuccessCount)
	t.Logf("  Failed chunks: %d", chunkErrorCount)
	t.Logf("  Total time: %v", totalTime)
	t.Logf("  Throughput: %.2f chunks/second", float64(chunkSuccessCount)/totalTime.Seconds())

	assert.Greater(t, successCount, concurrentUploads*8/10, "At least 80% of upload initiations should succeed")
	assert.Greater(t, chunkSuccessCount, concurrentUploads*chunksPerUpload*8/10, "At least 80% of chunks should upload successfully")
}

// TestLoad_ChunkedUploadStress teste le stress des uploads par chunks
func TestLoad_ChunkedUploadStress(t *testing.T) {
	if testing.Short() {
		t.Skip("Skipping load test in short mode")
	}

	router, db, redisClient, cleanup := setupLoadTestRouter(t)
	defer cleanup()

	if redisClient == nil {
		t.Skip("Redis not available, skipping chunked upload stress test")
	}

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

	// Test parameters - more aggressive
	numUploads := 10
	chunksPerUpload := 20
	chunkSize := 1024 * 100 // 100KB per chunk

	var wg sync.WaitGroup
	var mu sync.Mutex
	uploadIDs := make([]string, numUploads)

	startTime := time.Now()

	// Initiate uploads
	for i := 0; i < numUploads; i++ {
		reqBody := map[string]interface{}{
			"total_chunks": chunksPerUpload,
			"total_size":   int64(chunksPerUpload * chunkSize),
			"filename":     fmt.Sprintf("stress_test_%d.mp3", i),
		}
		body, _ := json.Marshal(reqBody)

		req := httptest.NewRequest("POST", "/api/v1/tracks/initiate", bytes.NewBuffer(body))
		req.Header.Set("Content-Type", "application/json")
		req.Header.Set("X-User-ID", userID.String())

		w := httptest.NewRecorder()
		router.ServeHTTP(w, req)

		if w.Code == http.StatusOK {
			var response map[string]interface{}
			json.Unmarshal(w.Body.Bytes(), &response)
			if data, ok := response["data"].(map[string]interface{}); ok {
				if uploadID, ok := data["upload_id"].(string); ok {
					uploadIDs[i] = uploadID
				}
			}
		}
	}

	// Upload all chunks concurrently (stress test)
	for uploadIndex, uploadID := range uploadIDs {
		if uploadID == "" {
			continue
		}
		for chunkNum := 1; chunkNum <= chunksPerUpload; chunkNum++ {
			wg.Add(1)
			go func(uploadIdx int, uploadID string, chunkNum int) {
				defer wg.Done()

				chunkData := createTestFile(t, chunkSize)
				body, contentType := createMultipartFormData(t, "chunk", fmt.Sprintf("chunk%d", chunkNum), chunkData)

				req := httptest.NewRequest("POST", "/api/v1/tracks/chunk", bytes.NewBuffer(body))
				req.Header.Set("Content-Type", contentType)
				req.Header.Set("X-User-ID", userID.String())
				req.ParseMultipartForm(10 << 20)
				req.Form.Set("upload_id", uploadID)
				req.Form.Set("chunk_number", fmt.Sprintf("%d", chunkNum))
				req.Form.Set("total_chunks", fmt.Sprintf("%d", chunksPerUpload))
				req.Form.Set("total_size", fmt.Sprintf("%d", chunksPerUpload*chunkSize))
				req.Form.Set("filename", fmt.Sprintf("stress_test_%d.mp3", uploadIdx))

				w := httptest.NewRecorder()
				router.ServeHTTP(w, req)

				mu.Lock()
				// Count successes/failures if needed
				mu.Unlock()
			}(uploadIndex, uploadID, chunkNum)
		}
	}

	wg.Wait()
	totalTime := time.Since(startTime)

	totalChunks := numUploads * chunksPerUpload
	t.Logf("Chunked Upload Stress Test Results:")
	t.Logf("  Total uploads: %d", numUploads)
	t.Logf("  Chunks per upload: %d", chunksPerUpload)
	t.Logf("  Total chunks: %d", totalChunks)
	t.Logf("  Total time: %v", totalTime)
	t.Logf("  Throughput: %.2f chunks/second", float64(totalChunks)/totalTime.Seconds())

	assert.Less(t, totalTime, 60*time.Second, "Stress test should complete within 60 seconds")
}

// TestLoad_UploadStatusPolling teste le polling du statut d'upload sous charge
func TestLoad_UploadStatusPolling(t *testing.T) {
	if testing.Short() {
		t.Skip("Skipping load test in short mode")
	}

	router, db, _, cleanup := setupLoadTestRouter(t)
	defer cleanup()

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

	trackID := uuid.New()
	track := &models.Track{
		ID:     trackID,
		UserID: userID,
		Title:  "Test Track",
		Status:  models.TrackStatusUploading,
	}
	db.Create(track)

	// Test parameters
	concurrentPolls := 50

	var wg sync.WaitGroup
	var mu sync.Mutex
	successCount := 0

	startTime := time.Now()

	for i := 0; i < concurrentPolls; i++ {
		wg.Add(1)
		go func() {
			defer wg.Done()

			req := httptest.NewRequest("GET", fmt.Sprintf("/api/v1/tracks/%s/status", trackID), nil)
			req.Header.Set("X-User-ID", userID.String())

			w := httptest.NewRecorder()
			router.ServeHTTP(w, req)

			mu.Lock()
			if w.Code == http.StatusOK {
				successCount++
			}
			mu.Unlock()
		}()
	}

	wg.Wait()
	totalTime := time.Since(startTime)

	t.Logf("Upload Status Polling Test Results:")
	t.Logf("  Total polls: %d", concurrentPolls)
	t.Logf("  Successful: %d", successCount)
	t.Logf("  Total time: %v", totalTime)
	t.Logf("  Throughput: %.2f polls/second", float64(concurrentPolls)/totalTime.Seconds())

	assert.Equal(t, concurrentPolls, successCount, "All status polls should succeed")
	assert.Less(t, totalTime, 5*time.Second, "Status polling should complete quickly")
}

