//go:build performance
// +build performance

package performance

import (
	"bytes"
	"encoding/json"
	"fmt"
	"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/auth"
	"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/repositories"
	"veza-backend-api/internal/services"
	"veza-backend-api/internal/validators"
)

// PerformanceThresholds définit les seuils de performance acceptables
var PerformanceThresholds = struct {
	HealthCheck      time.Duration // Health check should be very fast
	AuthLogin        time.Duration // Authentication should be fast
	AuthRegister     time.Duration // Registration can be slightly slower
	TrackList        time.Duration // List operations should be fast
	TrackGet         time.Duration // Get single item should be fast
	TrackCreate      time.Duration // Create operations can be slower
	PlaylistList     time.Duration // List operations should be fast
	PlaylistCreate   time.Duration // Create operations can be slower
	UserList         time.Duration // List operations should be fast
	UserGet          time.Duration // Get single item should be fast
}{
	HealthCheck:      10 * time.Millisecond,
	AuthLogin:        100 * time.Millisecond,
	AuthRegister:     200 * time.Millisecond,
	TrackList:        50 * time.Millisecond,
	TrackGet:         30 * time.Millisecond,
	TrackCreate:      500 * time.Millisecond,
	PlaylistList:     50 * time.Millisecond,
	PlaylistCreate:   200 * time.Millisecond,
	UserList:         50 * time.Millisecond,
	UserGet:         30 * time.Millisecond,
}

// setupPerformanceTestRouter crée un router de test pour les tests de performance
func setupPerformanceTestRouter(t *testing.T) (*gin.Engine, *gorm.DB, 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}

	// Setup services
	emailValidator := validators.NewEmailValidator(db)
	passwordValidator := validators.NewPasswordValidator()
	passwordService := services.NewPasswordService(dbWrapper, logger)
	jwtService, err := services.NewJWTService("test-secret-key-must-be-32-chars-long", "test-issuer", "test-audience")
	require.NoError(t, err)
	refreshTokenService := services.NewRefreshTokenService(db)
	emailVerificationService := services.NewEmailVerificationService(dbWrapper, logger)
	passwordResetService := services.NewPasswordResetService(dbWrapper, logger)
	emailService := services.NewEmailService(dbWrapper, logger)

	authService := auth.NewAuthService(
		db, emailValidator, passwordValidator, passwordService, jwtService,
		refreshTokenService, emailVerificationService, passwordResetService,
		emailService, nil, logger,
	)

	// Setup track services
	uploadDir := t.TempDir()
	trackService := track.NewTrackService(db, logger, uploadDir)
	trackUploadService := services.NewTrackUploadService(db, logger)
	likeService := services.NewTrackLikeService(db, logger)
	streamService := services.NewStreamService("http://localhost:8082", logger)
	trackHandler := track.NewTrackHandler(trackService, trackUploadService, nil, likeService, streamService)

	// Setup playlist services
	playlistRepo := repositories.NewPlaylistRepository(db)
	playlistTrackRepo := repositories.NewPlaylistTrackRepository(db)
	playlistCollaboratorRepo := repositories.NewPlaylistCollaboratorRepository(db)
	userRepo := repositories.NewGormUserRepository(db)
	playlistService := services.NewPlaylistService(playlistRepo, playlistTrackRepo, playlistCollaboratorRepo, userRepo, logger)
	playlistHandler := handlers.NewPlaylistHandler(playlistService, db, logger)

	// Setup user services
	userService := services.NewUserServiceWithDB(userRepo, db)
	profileHandler := handlers.NewProfileHandler(userService, logger)

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

	// Health check endpoint
	router.GET("/health", func(c *gin.Context) {
		c.JSON(http.StatusOK, gin.H{"status": "ok"})
	})

	router.GET("/readyz", func(c *gin.Context) {
		c.JSON(http.StatusOK, gin.H{"status": "ready"})
	})

	// Auth routes
	authGroup := router.Group("/api/v1/auth")
	{
		authGroup.POST("/login", handlers.Login(authService, services.NewSessionService(dbWrapper, logger), services.NewTwoFactorService(dbWrapper, logger), logger))
		authGroup.POST("/register", handlers.Register(authService, logger))
	}

	// Track routes
	tracksGroup := router.Group("/api/v1/tracks")
	{
		tracksGroup.GET("", trackHandler.ListTracks)
		tracksGroup.GET("/:id", trackHandler.GetTrack)
		tracksGroup.POST("", func(c *gin.Context) {
			// Mock upload for performance testing
			c.JSON(http.StatusCreated, gin.H{"id": uuid.New().String()})
		})
	}

	// Playlist routes
	playlistsGroup := router.Group("/api/v1/playlists")
	{
		playlistsGroup.GET("", playlistHandler.GetPlaylists)
		playlistsGroup.POST("", func(c *gin.Context) {
			// Mock create for performance testing
			c.JSON(http.StatusCreated, gin.H{"id": uuid.New().String()})
		})
	}

	// User routes
	usersGroup := router.Group("/api/v1/users")
	{
		usersGroup.GET("", profileHandler.ListUsers)
		usersGroup.GET("/:id", profileHandler.GetProfile)
	}

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

	return router, db, cleanup
}

// measureResponseTime mesure le temps de réponse d'une requête HTTP
func measureResponseTime(router *gin.Engine, method, path string, body []byte, headers map[string]string) time.Duration {
	req := httptest.NewRequest(method, path, bytes.NewBuffer(body))
	for k, v := range headers {
		req.Header.Set(k, v)
	}
	req.Header.Set("Content-Type", "application/json")

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

	return duration
}

// TestPerformance_HealthCheck teste les performances de l'endpoint health check
func TestPerformance_HealthCheck(t *testing.T) {
	if testing.Short() {
		t.Skip("Skipping performance test in short mode")
	}

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

	// Run multiple requests to get average
	var totalDuration time.Duration
	iterations := 100

	for i := 0; i < iterations; i++ {
		duration := measureResponseTime(router, "GET", "/health", nil, nil)
		totalDuration += duration
	}

	avgDuration := totalDuration / time.Duration(iterations)
	t.Logf("Health check average response time: %v (threshold: %v)", avgDuration, PerformanceThresholds.HealthCheck)

	assert.Less(t, avgDuration, PerformanceThresholds.HealthCheck,
		"Health check should respond within threshold")
}

// TestPerformance_ReadinessCheck teste les performances de l'endpoint readiness check
func TestPerformance_ReadinessCheck(t *testing.T) {
	if testing.Short() {
		t.Skip("Skipping performance test in short mode")
	}

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

	var totalDuration time.Duration
	iterations := 100

	for i := 0; i < iterations; i++ {
		duration := measureResponseTime(router, "GET", "/readyz", nil, nil)
		totalDuration += duration
	}

	avgDuration := totalDuration / time.Duration(iterations)
	t.Logf("Readiness check average response time: %v (threshold: %v)", avgDuration, PerformanceThresholds.HealthCheck)

	assert.Less(t, avgDuration, PerformanceThresholds.HealthCheck,
		"Readiness check should respond within threshold")
}

// TestPerformance_AuthLogin teste les performances de l'endpoint de login
func TestPerformance_AuthLogin(t *testing.T) {
	if testing.Short() {
		t.Skip("Skipping performance test in short mode")
	}

	router, db, cleanup := setupPerformanceTestRouter(t)
	defer cleanup()

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

	loginReq := map[string]interface{}{
		"email":    "test@example.com",
		"password": "password123",
	}
	loginBody, _ := json.Marshal(loginReq)

	var totalDuration time.Duration
	iterations := 50

	for i := 0; i < iterations; i++ {
		duration := measureResponseTime(router, "POST", "/api/v1/auth/login", loginBody, nil)
		totalDuration += duration
	}

	avgDuration := totalDuration / time.Duration(iterations)
	t.Logf("Auth login average response time: %v (threshold: %v)", avgDuration, PerformanceThresholds.AuthLogin)

	assert.Less(t, avgDuration, PerformanceThresholds.AuthLogin,
		"Auth login should respond within threshold")
}

// TestPerformance_AuthRegister teste les performances de l'endpoint d'inscription
func TestPerformance_AuthRegister(t *testing.T) {
	if testing.Short() {
		t.Skip("Skipping performance test in short mode")
	}

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

	registerReq := map[string]interface{}{
		"email":           fmt.Sprintf("test%d@example.com", time.Now().UnixNano()),
		"username":        fmt.Sprintf("testuser%d", time.Now().UnixNano()),
		"password":         "SecurePassword123!",
		"password_confirm": "SecurePassword123!",
	}
	registerBody, _ := json.Marshal(registerReq)

	var totalDuration time.Duration
	iterations := 20

	for i := 0; i < iterations; i++ {
		// Use unique email for each iteration
		registerReq["email"] = fmt.Sprintf("test%d@example.com", time.Now().UnixNano()+int64(i))
		registerReq["username"] = fmt.Sprintf("testuser%d", time.Now().UnixNano()+int64(i))
		registerBody, _ = json.Marshal(registerReq)

		duration := measureResponseTime(router, "POST", "/api/v1/auth/register", registerBody, nil)
		totalDuration += duration
	}

	avgDuration := totalDuration / time.Duration(iterations)
	t.Logf("Auth register average response time: %v (threshold: %v)", avgDuration, PerformanceThresholds.AuthRegister)

	assert.Less(t, avgDuration, PerformanceThresholds.AuthRegister,
		"Auth register should respond within threshold")
}

// TestPerformance_TrackList teste les performances de l'endpoint de liste de tracks
func TestPerformance_TrackList(t *testing.T) {
	if testing.Short() {
		t.Skip("Skipping performance test in short mode")
	}

	router, db, cleanup := setupPerformanceTestRouter(t)
	defer cleanup()

	// Create test tracks
	userID := uuid.New()
	for i := 0; i < 10; i++ {
		track := &models.Track{
			ID:     uuid.New(),
			UserID: userID,
			Title:  fmt.Sprintf("Test Track %d", i),
		}
		db.Create(track)
	}

	var totalDuration time.Duration
	iterations := 100

	for i := 0; i < iterations; i++ {
		duration := measureResponseTime(router, "GET", "/api/v1/tracks", nil, nil)
		totalDuration += duration
	}

	avgDuration := totalDuration / time.Duration(iterations)
	t.Logf("Track list average response time: %v (threshold: %v)", avgDuration, PerformanceThresholds.TrackList)

	assert.Less(t, avgDuration, PerformanceThresholds.TrackList,
		"Track list should respond within threshold")
}

// TestPerformance_TrackGet teste les performances de l'endpoint de récupération d'un track
func TestPerformance_TrackGet(t *testing.T) {
	if testing.Short() {
		t.Skip("Skipping performance test in short mode")
	}

	router, db, cleanup := setupPerformanceTestRouter(t)
	defer cleanup()

	// Create test track
	userID := uuid.New()
	trackID := uuid.New()
	track := &models.Track{
		ID:     trackID,
		UserID: userID,
		Title:  "Test Track",
	}
	err := db.Create(track).Error
	require.NoError(t, err)

	var totalDuration time.Duration
	iterations := 100

	for i := 0; i < iterations; i++ {
		duration := measureResponseTime(router, "GET", fmt.Sprintf("/api/v1/tracks/%s", trackID), nil, nil)
		totalDuration += duration
	}

	avgDuration := totalDuration / time.Duration(iterations)
	t.Logf("Track get average response time: %v (threshold: %v)", avgDuration, PerformanceThresholds.TrackGet)

	assert.Less(t, avgDuration, PerformanceThresholds.TrackGet,
		"Track get should respond within threshold")
}

// TestPerformance_TrackCreate teste les performances de l'endpoint de création de track
func TestPerformance_TrackCreate(t *testing.T) {
	if testing.Short() {
		t.Skip("Skipping performance test in short mode")
	}

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

	createReq := map[string]interface{}{
		"title": "Test Track",
	}
	createBody, _ := json.Marshal(createReq)

	var totalDuration time.Duration
	iterations := 50

	for i := 0; i < iterations; i++ {
		duration := measureResponseTime(router, "POST", "/api/v1/tracks", createBody, nil)
		totalDuration += duration
	}

	avgDuration := totalDuration / time.Duration(iterations)
	t.Logf("Track create average response time: %v (threshold: %v)", avgDuration, PerformanceThresholds.TrackCreate)

	assert.Less(t, avgDuration, PerformanceThresholds.TrackCreate,
		"Track create should respond within threshold")
}

// TestPerformance_PlaylistList teste les performances de l'endpoint de liste de playlists
func TestPerformance_PlaylistList(t *testing.T) {
	if testing.Short() {
		t.Skip("Skipping performance test in short mode")
	}

	router, db, cleanup := setupPerformanceTestRouter(t)
	defer cleanup()

	// Create test playlists
	userID := uuid.New()
	for i := 0; i < 10; i++ {
		playlist := &models.Playlist{
			ID:     uuid.New(),
			UserID: userID,
			Title:  fmt.Sprintf("Test Playlist %d", i),
		}
		db.Create(playlist)
	}

	var totalDuration time.Duration
	iterations := 100

	for i := 0; i < iterations; i++ {
		duration := measureResponseTime(router, "GET", "/api/v1/playlists", nil, nil)
		totalDuration += duration
	}

	avgDuration := totalDuration / time.Duration(iterations)
	t.Logf("Playlist list average response time: %v (threshold: %v)", avgDuration, PerformanceThresholds.PlaylistList)

	assert.Less(t, avgDuration, PerformanceThresholds.PlaylistList,
		"Playlist list should respond within threshold")
}

// TestPerformance_PlaylistCreate teste les performances de l'endpoint de création de playlist
func TestPerformance_PlaylistCreate(t *testing.T) {
	if testing.Short() {
		t.Skip("Skipping performance test in short mode")
	}

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

	createReq := map[string]interface{}{
		"name": "Test Playlist",
	}
	createBody, _ := json.Marshal(createReq)

	var totalDuration time.Duration
	iterations := 50

	for i := 0; i < iterations; i++ {
		duration := measureResponseTime(router, "POST", "/api/v1/playlists", createBody, nil)
		totalDuration += duration
	}

	avgDuration := totalDuration / time.Duration(iterations)
	t.Logf("Playlist create average response time: %v (threshold: %v)", avgDuration, PerformanceThresholds.PlaylistCreate)

	assert.Less(t, avgDuration, PerformanceThresholds.PlaylistCreate,
		"Playlist create should respond within threshold")
}

// TestPerformance_UserList teste les performances de l'endpoint de liste d'utilisateurs
func TestPerformance_UserList(t *testing.T) {
	if testing.Short() {
		t.Skip("Skipping performance test in short mode")
	}

	router, db, cleanup := setupPerformanceTestRouter(t)
	defer cleanup()

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

	var totalDuration time.Duration
	iterations := 100

	for i := 0; i < iterations; i++ {
		duration := measureResponseTime(router, "GET", "/api/v1/users", nil, nil)
		totalDuration += duration
	}

	avgDuration := totalDuration / time.Duration(iterations)
	t.Logf("User list average response time: %v (threshold: %v)", avgDuration, PerformanceThresholds.UserList)

	assert.Less(t, avgDuration, PerformanceThresholds.UserList,
		"User list should respond within threshold")
}

// TestPerformance_UserGet teste les performances de l'endpoint de récupération d'un utilisateur
func TestPerformance_UserGet(t *testing.T) {
	if testing.Short() {
		t.Skip("Skipping performance test in short mode")
	}

	router, db, cleanup := setupPerformanceTestRouter(t)
	defer cleanup()

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

	var totalDuration time.Duration
	iterations := 100

	for i := 0; i < iterations; i++ {
		duration := measureResponseTime(router, "GET", fmt.Sprintf("/api/v1/users/%s", userID), nil, nil)
		totalDuration += duration
	}

	avgDuration := totalDuration / time.Duration(iterations)
	t.Logf("User get average response time: %v (threshold: %v)", avgDuration, PerformanceThresholds.UserGet)

	assert.Less(t, avgDuration, PerformanceThresholds.UserGet,
		"User get should respond within threshold")
}

// BenchmarkHealthCheck benchmark pour l'endpoint health check
func BenchmarkHealthCheck(b *testing.B) {
	gin.SetMode(gin.TestMode)
	router := gin.New()
	router.GET("/health", func(c *gin.Context) {
		c.JSON(http.StatusOK, gin.H{"status": "ok"})
	})

	b.ResetTimer()
	for i := 0; i < b.N; i++ {
		req := httptest.NewRequest("GET", "/health", nil)
		w := httptest.NewRecorder()
		router.ServeHTTP(w, req)
	}
}

// BenchmarkTrackList benchmark pour l'endpoint de liste de tracks
func BenchmarkTrackList(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.Track{})

	// Create test tracks
	userID := uuid.New()
	for i := 0; i < 100; i++ {
		track := &models.Track{
			ID:     uuid.New(),
			UserID: userID,
			Title:  fmt.Sprintf("Test Track %d", i),
		}
		db.Create(track)
	}

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

	router := gin.New()
	router.GET("/api/v1/tracks", trackHandler.ListTracks)

	b.ResetTimer()
	for i := 0; i < b.N; i++ {
		req := httptest.NewRequest("GET", "/api/v1/tracks", nil)
		w := httptest.NewRecorder()
		router.ServeHTTP(w, req)
	}
}

// BenchmarkTrackGet benchmark pour l'endpoint de récupération d'un track
func BenchmarkTrackGet(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.Track{})

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

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

	router := gin.New()
	router.GET("/api/v1/tracks/:id", trackHandler.GetTrack)

	b.ResetTimer()
	for i := 0; i < b.N; i++ {
		req := httptest.NewRequest("GET", fmt.Sprintf("/api/v1/tracks/%s", trackID), nil)
		w := httptest.NewRecorder()
		router.ServeHTTP(w, req)
	}
}

