//go:build performance
// +build performance

package performance

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

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

	"veza-backend-api/internal/core/track"
	"veza-backend-api/internal/database"
	"veza-backend-api/internal/handlers"
	"veza-backend-api/internal/models"
	"veza-backend-api/internal/services"
)

// UploadPerformanceThresholds définit les seuils de performance pour les uploads
var UploadPerformanceThresholds = struct {
	SimpleUploadInitiate  time.Duration // Initiation d'upload simple
	SimpleUploadComplete  time.Duration // Upload simple complet
	ChunkedUploadInitiate time.Duration // Initiation upload chunked
	ChunkedUploadChunk    time.Duration // Upload d'un chunk
	ChunkedUploadComplete time.Duration // Complétion upload chunked
	BatchUpload           time.Duration // Upload batch
}{
	SimpleUploadInitiate:  100 * time.Millisecond,
	SimpleUploadComplete:  2000 * time.Millisecond, // 2s pour upload complet
	ChunkedUploadInitiate: 100 * time.Millisecond,
	ChunkedUploadChunk:    200 * time.Millisecond,  // 200ms par chunk
	ChunkedUploadComplete: 3000 * time.Millisecond, // 3s pour assembler
	BatchUpload:           1500 * time.Millisecond,
}

// setupUploadTestRouter crée un router de test pour les tests d'upload
func setupUploadTestRouter(t *testing.T) (*gin.Engine, *gorm.DB, uuid.UUID, 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.Playlist{},
		&models.PlaylistTrack{},
		&models.RefreshToken{},
		&models.Session{},
		&models.Role{},
		&models.UserRole{},
		&models.Permission{},
	)
	require.NoError(t, err)

	dbWrapper := &database.Database{GormDB: db}

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

	// Setup services
	uploadDir := t.TempDir()
	chunksDir := t.TempDir()
	trackService := track.NewTrackService(db, logger, uploadDir)
	trackUploadService := services.NewTrackUploadService(db, logger)
	// NewTrackChunkService takes (chunksDir string, redisClient *redis.Client, logger *zap.Logger)
	chunkService := services.NewTrackChunkService(chunksDir, nil, logger) // nil Redis for performance tests
	likeService := services.NewTrackLikeService(db, logger)
	streamService := services.NewStreamService("http://localhost:8082", logger)
	trackHandler := track.NewTrackHandler(trackService, trackUploadService, chunkService, likeService, streamService)

	// Setup upload handler
	uploadConfig := &services.UploadConfig{
		MaxAudioSize:      100 * 1024 * 1024, // 100MB
		AllowedAudioTypes: []string{"audio/mpeg", "audio/mp3"},
		ClamAVEnabled:     false, // Disable for performance tests
		ClamAVRequired:    false,
		ClamAVAddress:     "",
		QuarantineDir:     t.TempDir(),
	}
	uploadValidator, err := services.NewUploadValidator(uploadConfig, logger)
	require.NoError(t, err)
	auditService := services.NewAuditService(dbWrapper, logger)
	uploadHandler := handlers.NewUploadHandler(uploadValidator, auditService, trackUploadService, logger, 10)

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

	// Mock auth middleware - set user_id in context
	router.Use(func(c *gin.Context) {
		c.Set("user_id", userID)
		c.Next()
	})

	// Track 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)
	}

	// Upload routes
	uploadsGroup := router.Group("/api/v1/uploads")
	{
		uploadsGroup.POST("", uploadHandler.UploadFile())
		uploadsGroup.POST("/batch", uploadHandler.BatchUpload())
	}

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

	return router, db, userID, cleanup
}

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

// createMultipartForm crée un multipart form avec un fichier
func createMultipartForm(filename string, fileData *bytes.Buffer, fields map[string]string) (string, *bytes.Buffer) {
	body := &bytes.Buffer{}
	writer := multipart.NewWriter(body)

	// Add file
	fileWriter, err := writer.CreateFormFile("file", filename)
	if err != nil {
		panic(err)
	}
	io.Copy(fileWriter, fileData)

	// Add fields
	for key, value := range fields {
		writer.WriteField(key, value)
	}

	writer.Close()
	return writer.FormDataContentType(), body
}

// TestPerformance_SimpleUpload teste les performances d'upload simple
func TestPerformance_SimpleUpload(t *testing.T) {
	if testing.Short() {
		t.Skip("Skipping performance test in short mode")
	}

	router, _, _, cleanup := setupUploadTestRouter(t)
	defer cleanup()

	fileSize := 1024 * 1024 // 1MB
	fileData := createTestFile(fileSize)
	fields := map[string]string{
		"title":     "Test Track",
		"artist":    "Test Artist",
		"file_type": "audio",
	}
	contentType, body := createMultipartForm("test.mp3", fileData, fields)

	var totalDuration time.Duration
	iterations := 20

	for i := 0; i < iterations; i++ {
		req := httptest.NewRequest(http.MethodPost, "/api/v1/tracks", body)
		req.Header.Set("Content-Type", contentType)
		w := httptest.NewRecorder()

		start := time.Now()
		router.ServeHTTP(w, req)
		duration := time.Since(start)
		totalDuration += duration

		// Reset body for next iteration
		fileData = createTestFile(fileSize)
		_, body = createMultipartForm("test.mp3", fileData, fields)
	}

	avgDuration := totalDuration / time.Duration(iterations)
	t.Logf("Simple upload average response time: %v (threshold: %v)", avgDuration, UploadPerformanceThresholds.SimpleUploadComplete)

	assert.Less(t, avgDuration, UploadPerformanceThresholds.SimpleUploadComplete,
		"Simple upload should complete within threshold")
}

// TestPerformance_ChunkedUploadInitiate teste les performances d'initiation d'upload chunked
func TestPerformance_ChunkedUploadInitiate(t *testing.T) {
	if testing.Short() {
		t.Skip("Skipping performance test in short mode")
	}

	router, _, _, cleanup := setupUploadTestRouter(t)
	defer cleanup()

	payload := map[string]interface{}{
		"total_chunks": 5,
		"total_size":   5 * 1024 * 1024, // 5MB
		"filename":     "test.mp3",
	}
	payloadBody, _ := json.Marshal(payload)

	var totalDuration time.Duration
	iterations := 100

	for i := 0; i < iterations; i++ {
		req := httptest.NewRequest(http.MethodPost, "/api/v1/tracks/initiate", bytes.NewBuffer(payloadBody))
		req.Header.Set("Content-Type", "application/json")
		w := httptest.NewRecorder()

		start := time.Now()
		router.ServeHTTP(w, req)
		duration := time.Since(start)
		totalDuration += duration
	}

	avgDuration := totalDuration / time.Duration(iterations)
	t.Logf("Chunked upload initiate average response time: %v (threshold: %v)", avgDuration, UploadPerformanceThresholds.ChunkedUploadInitiate)

	assert.Less(t, avgDuration, UploadPerformanceThresholds.ChunkedUploadInitiate,
		"Chunked upload initiate should respond within threshold")
}

// TestPerformance_ChunkedUploadChunk teste les performances d'upload d'un chunk
func TestPerformance_ChunkedUploadChunk(t *testing.T) {
	if testing.Short() {
		t.Skip("Skipping performance test in short mode")
	}

	router, _, _, cleanup := setupUploadTestRouter(t)
	defer cleanup()

	// First, initiate an upload
	initiatePayload := map[string]interface{}{
		"total_chunks": 5,
		"total_size":   5 * 1024 * 1024,
		"filename":     "test.mp3",
	}
	initiateBody, _ := json.Marshal(initiatePayload)
	initiateReq := httptest.NewRequest(http.MethodPost, "/api/v1/tracks/initiate", bytes.NewBuffer(initiateBody))
	initiateReq.Header.Set("Content-Type", "application/json")
	initiateW := httptest.NewRecorder()
	router.ServeHTTP(initiateW, initiateReq)

	var initiateResp map[string]interface{}
	json.Unmarshal(initiateW.Body.Bytes(), &initiateResp)
	uploadID := initiateResp["data"].(map[string]interface{})["upload_id"].(string)

	chunkSize := 1024 * 1024 // 1MB per chunk
	chunkData := createTestFile(chunkSize)

	var totalDuration time.Duration
	iterations := 50

	for i := 0; i < iterations; i++ {
		// Create multipart form for chunk
		body := &bytes.Buffer{}
		writer := multipart.NewWriter(body)

		fileWriter, _ := writer.CreateFormFile("chunk", "chunk.bin")
		io.Copy(fileWriter, chunkData)

		writer.WriteField("upload_id", uploadID)
		writer.WriteField("chunk_number", "1")
		writer.WriteField("total_chunks", "5")
		writer.WriteField("total_size", fmt.Sprintf("%d", 5*1024*1024))
		writer.WriteField("filename", "test.mp3")
		writer.Close()

		req := httptest.NewRequest(http.MethodPost, "/api/v1/tracks/chunk", body)
		req.Header.Set("Content-Type", writer.FormDataContentType())
		w := httptest.NewRecorder()

		start := time.Now()
		router.ServeHTTP(w, req)
		duration := time.Since(start)
		totalDuration += duration

		// Reset chunk data
		chunkData = createTestFile(chunkSize)
	}

	avgDuration := totalDuration / time.Duration(iterations)
	t.Logf("Chunk upload average response time: %v (threshold: %v)", avgDuration, UploadPerformanceThresholds.ChunkedUploadChunk)

	assert.Less(t, avgDuration, UploadPerformanceThresholds.ChunkedUploadChunk,
		"Chunk upload should respond within threshold")
}

// TestPerformance_ConcurrentUploads teste les performances avec uploads concurrents
func TestPerformance_ConcurrentUploads(t *testing.T) {
	if testing.Short() {
		t.Skip("Skipping performance test in short mode")
	}

	router, _, _, cleanup := setupUploadTestRouter(t)
	defer cleanup()

	fileSize := 512 * 1024 // 512KB
	concurrentUploads := 10
	iterations := 5

	var totalDuration time.Duration

	for iter := 0; iter < iterations; iter++ {
		start := time.Now()

		// Simulate concurrent uploads using channels
		done := make(chan bool, concurrentUploads)
		for i := 0; i < concurrentUploads; i++ {
			go func(id int) {
				fileData := createTestFile(fileSize)
				fields := map[string]string{
					"title":     fmt.Sprintf("Test Track %d", id),
					"artist":    "Test Artist",
					"file_type": "audio",
				}
				contentType, body := createMultipartForm(fmt.Sprintf("test%d.mp3", id), fileData, fields)

				req := httptest.NewRequest(http.MethodPost, "/api/v1/tracks", body)
				req.Header.Set("Content-Type", contentType)
				w := httptest.NewRecorder()

				router.ServeHTTP(w, req)
				done <- true
			}(i)
		}

		// Wait for all uploads to complete
		for i := 0; i < concurrentUploads; i++ {
			<-done
		}

		duration := time.Since(start)
		totalDuration += duration
	}

	avgDuration := totalDuration / time.Duration(iterations)
	t.Logf("Concurrent uploads (%d) average time: %v", concurrentUploads, avgDuration)

	// Threshold: should handle 10 concurrent uploads in reasonable time
	threshold := UploadPerformanceThresholds.SimpleUploadComplete * time.Duration(concurrentUploads) / 2
	assert.Less(t, avgDuration, threshold,
		"Concurrent uploads should complete within reasonable time")
}

// BenchmarkSimpleUpload benchmark pour upload simple
func BenchmarkSimpleUpload(b *testing.B) {
	gin.SetMode(gin.TestMode)
	logger := zap.NewNop()

	db, err := gorm.Open(sqlite.Open(":memory:"), &gorm.Config{})
	if err != nil {
		b.Fatalf("Failed to open database: %v", err)
	}
	db.AutoMigrate(&models.User{}, &models.Track{})

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

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

	router := gin.New()
	router.Use(func(c *gin.Context) {
		c.Set("user_id", userID)
		c.Next()
	})
	router.POST("/api/v1/tracks", trackHandler.UploadTrack)

	fileSize := 1024 * 1024 // 1MB
	fileData := createTestFile(fileSize)
	fields := map[string]string{
		"title":     "Test Track",
		"artist":    "Test Artist",
		"file_type": "audio",
	}
	contentType, body := createMultipartForm("test.mp3", fileData, fields)

	b.ResetTimer()
	for i := 0; i < b.N; i++ {
		req := httptest.NewRequest(http.MethodPost, "/api/v1/tracks", body)
		req.Header.Set("Content-Type", contentType)
		w := httptest.NewRecorder()
		router.ServeHTTP(w, req)

		// Reset body for next iteration
		fileData = createTestFile(fileSize)
		_, body = createMultipartForm("test.mp3", fileData, fields)
	}
}
