//go:build integration || pagination
// +build integration pagination

package pagination

import (
	"encoding/json"
	"fmt"
	"net/http"
	"net/http/httptest"
	"testing"

	"github.com/gin-gonic/gin"
	"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/core/track"
	"veza-backend-api/internal/handlers"
	"veza-backend-api/internal/models"
	"veza-backend-api/internal/repositories"
	"veza-backend-api/internal/services"
)

// setupPaginationTestRouter crée un router de test avec des données pour tester la pagination
func setupPaginationTestRouter(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{},
	)
	require.NoError(t, err)

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

	// Create 50 tracks for pagination testing
	uploadDir := t.TempDir()
	for i := 0; i < 50; i++ {
		track := &models.Track{
			ID:       uuid.New(),
			UserID:   userID,
			Title:    fmt.Sprintf("Test Track %d", i+1),
			IsPublic: true,
		}
		err = db.Create(track).Error
		require.NoError(t, err)
	}

	// Create 50 playlists for pagination testing
	for i := 0; i < 50; i++ {
		playlist := &models.Playlist{
			ID:       uuid.New(),
			UserID:   userID,
			Title:    fmt.Sprintf("Test Playlist %d", i+1),
			IsPublic: true,
		}
		err = db.Create(playlist).Error
		require.NoError(t, err)
	}

	// Create 10 additional users for pagination testing
	for i := 0; i < 10; i++ {
		user := &models.User{
			ID:           uuid.New(),
			Email:        fmt.Sprintf("user%d@example.com", i+1),
			Username:     fmt.Sprintf("user%d", i+1),
			PasswordHash: "$2a$10$abcdefghijklmnopqrstuvwxyz1234567890",
			IsVerified:   true,
		}
		err = db.Create(user).Error
		require.NoError(t, err)
	}

	// Setup services (minimal setup for pagination tests)

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

	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)

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

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

	// Public routes
	tracks := router.Group("/api/v1/tracks")
	{
		tracks.GET("", trackHandler.ListTracks)
	}

	users := router.Group("/api/v1/users")
	{
		users.GET("", profileHandler.ListUsers)
	}

	// Protected routes (with mock auth)
	protected := router.Group("/api/v1")
	protected.Use(func(c *gin.Context) {
		c.Set("user_id", userID)
		c.Next()
	})

	playlists := protected.Group("/playlists")
	{
		playlists.GET("", playlistHandler.GetPlaylists)
	}

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

	return router, db, userID, cleanup
}

// TestPagination_Tracks_Default teste la pagination par défaut pour les tracks
func TestPagination_Tracks_Default(t *testing.T) {
	router, _, _, cleanup := setupPaginationTestRouter(t)
	defer cleanup()

	req := httptest.NewRequest(http.MethodGet, "/api/v1/tracks", nil)
	w := httptest.NewRecorder()
	router.ServeHTTP(w, req)

	assert.Equal(t, http.StatusOK, w.Code)

	var resp map[string]interface{}
	err := json.Unmarshal(w.Body.Bytes(), &resp)
	require.NoError(t, err)

	assert.True(t, resp["success"].(bool))

	data, ok := resp["data"].(map[string]interface{})
	require.True(t, ok)

	tracks, ok := data["tracks"].([]interface{})
	require.True(t, ok)
	assert.LessOrEqual(t, len(tracks), 20, "Default limit should be 20")

	pagination, ok := data["pagination"].(map[string]interface{})
	require.True(t, ok)
	assert.Equal(t, float64(1), pagination["page"], "Default page should be 1")
	assert.Equal(t, float64(20), pagination["limit"], "Default limit should be 20")
}

// TestPagination_Tracks_CustomParams teste la pagination avec des paramètres personnalisés
func TestPagination_Tracks_CustomParams(t *testing.T) {
	router, _, _, cleanup := setupPaginationTestRouter(t)
	defer cleanup()

	req := httptest.NewRequest(http.MethodGet, "/api/v1/tracks?page=2&limit=10", nil)
	w := httptest.NewRecorder()
	router.ServeHTTP(w, req)

	assert.Equal(t, http.StatusOK, w.Code)

	var resp map[string]interface{}
	err := json.Unmarshal(w.Body.Bytes(), &resp)
	require.NoError(t, err)

	data, ok := resp["data"].(map[string]interface{})
	require.True(t, ok)

	tracks, ok := data["tracks"].([]interface{})
	require.True(t, ok)
	assert.LessOrEqual(t, len(tracks), 10, "Should return at most 10 tracks")

	pagination, ok := data["pagination"].(map[string]interface{})
	require.True(t, ok)
	assert.Equal(t, float64(2), pagination["page"], "Page should be 2")
	assert.Equal(t, float64(10), pagination["limit"], "Limit should be 10")
}

// TestPagination_Tracks_InvalidParams teste la validation des paramètres invalides
func TestPagination_Tracks_InvalidParams(t *testing.T) {
	router, _, _, cleanup := setupPaginationTestRouter(t)
	defer cleanup()

	tests := []struct {
		name          string
		query         string
		expectedPage  float64
		expectedLimit float64
	}{
		{
			name:          "Negative page",
			query:         "page=-1&limit=10",
			expectedPage:  1,
			expectedLimit: 10,
		},
		{
			name:          "Zero page",
			query:         "page=0&limit=10",
			expectedPage:  1,
			expectedLimit: 10,
		},
		{
			name:          "Negative limit",
			query:         "page=1&limit=-5",
			expectedPage:  1,
			expectedLimit: 20,
		},
		{
			name:          "Zero limit",
			query:         "page=1&limit=0",
			expectedPage:  1,
			expectedLimit: 20,
		},
		{
			name:          "Limit too high",
			query:         "page=1&limit=200",
			expectedPage:  1,
			expectedLimit: 20, // Should default to 20 or max 100
		},
		{
			name:          "Invalid page format",
			query:         "page=abc&limit=10",
			expectedPage:  1,
			expectedLimit: 10,
		},
		{
			name:          "Invalid limit format",
			query:         "page=1&limit=xyz",
			expectedPage:  1,
			expectedLimit: 20,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			req := httptest.NewRequest(http.MethodGet, fmt.Sprintf("/api/v1/tracks?%s", tt.query), nil)
			w := httptest.NewRecorder()
			router.ServeHTTP(w, req)

			assert.Equal(t, http.StatusOK, w.Code)

			var resp map[string]interface{}
			err := json.Unmarshal(w.Body.Bytes(), &resp)
			require.NoError(t, err)

			data, ok := resp["data"].(map[string]interface{})
			require.True(t, ok)

			pagination, ok := data["pagination"].(map[string]interface{})
			require.True(t, ok)
			assert.Equal(t, tt.expectedPage, pagination["page"], "Page should be corrected")
			assert.Equal(t, tt.expectedLimit, pagination["limit"], "Limit should be corrected")
		})
	}
}

// TestPagination_Tracks_Metadata teste les métadonnées de pagination
func TestPagination_Tracks_Metadata(t *testing.T) {
	router, _, _, cleanup := setupPaginationTestRouter(t)
	defer cleanup()

	req := httptest.NewRequest(http.MethodGet, "/api/v1/tracks?page=1&limit=10", nil)
	w := httptest.NewRecorder()
	router.ServeHTTP(w, req)

	assert.Equal(t, http.StatusOK, w.Code)

	var resp map[string]interface{}
	err := json.Unmarshal(w.Body.Bytes(), &resp)
	require.NoError(t, err)

	data, ok := resp["data"].(map[string]interface{})
	require.True(t, ok)

	pagination, ok := data["pagination"].(map[string]interface{})
	require.True(t, ok)

	// Verify pagination metadata
	assert.Contains(t, pagination, "page")
	assert.Contains(t, pagination, "limit")
	assert.Contains(t, pagination, "total")
	assert.Contains(t, pagination, "total_pages")

	total := pagination["total"].(float64)
	assert.Greater(t, total, float64(0), "Total should be greater than 0")

	totalPages := pagination["total_pages"].(float64)
	assert.Greater(t, totalPages, float64(0), "Total pages should be greater than 0")
}

// TestPagination_Tracks_Navigation teste la navigation entre les pages
func TestPagination_Tracks_Navigation(t *testing.T) {
	router, _, _, cleanup := setupPaginationTestRouter(t)
	defer cleanup()

	// Get first page
	req1 := httptest.NewRequest(http.MethodGet, "/api/v1/tracks?page=1&limit=10", nil)
	w1 := httptest.NewRecorder()
	router.ServeHTTP(w1, req1)

	assert.Equal(t, http.StatusOK, w1.Code)

	var resp1 map[string]interface{}
	err := json.Unmarshal(w1.Body.Bytes(), &resp1)
	require.NoError(t, err)

	data1, _ := resp1["data"].(map[string]interface{})
	tracks1, _ := data1["tracks"].([]interface{})
	pagination1, _ := data1["pagination"].(map[string]interface{})

	// Get second page
	req2 := httptest.NewRequest(http.MethodGet, "/api/v1/tracks?page=2&limit=10", nil)
	w2 := httptest.NewRecorder()
	router.ServeHTTP(w2, req2)

	assert.Equal(t, http.StatusOK, w2.Code)

	var resp2 map[string]interface{}
	err = json.Unmarshal(w2.Body.Bytes(), &resp2)
	require.NoError(t, err)

	data2, _ := resp2["data"].(map[string]interface{})
	tracks2, _ := data2["tracks"].([]interface{})
	pagination2, _ := data2["pagination"].(map[string]interface{})

	// Verify pages are different
	assert.Equal(t, float64(1), pagination1["page"])
	assert.Equal(t, float64(2), pagination2["page"])

	// Verify tracks are different (if we have enough tracks)
	if len(tracks1) > 0 && len(tracks2) > 0 {
		track1ID := tracks1[0].(map[string]interface{})["id"].(string)
		track2ID := tracks2[0].(map[string]interface{})["id"].(string)
		assert.NotEqual(t, track1ID, track2ID, "Tracks should be different between pages")
	}
}

// TestPagination_Users_Default teste la pagination par défaut pour les users
func TestPagination_Users_Default(t *testing.T) {
	router, _, _, cleanup := setupPaginationTestRouter(t)
	defer cleanup()

	req := httptest.NewRequest(http.MethodGet, "/api/v1/users", nil)
	w := httptest.NewRecorder()
	router.ServeHTTP(w, req)

	assert.Equal(t, http.StatusOK, w.Code)

	var resp map[string]interface{}
	err := json.Unmarshal(w.Body.Bytes(), &resp)
	require.NoError(t, err)

	assert.True(t, resp["success"].(bool))

	data, ok := resp["data"].(map[string]interface{})
	require.True(t, ok)

	users, ok := data["users"].([]interface{})
	require.True(t, ok)
	assert.LessOrEqual(t, len(users), 20, "Default limit should be 20")

	pagination, ok := data["pagination"].(map[string]interface{})
	require.True(t, ok)
	assert.Equal(t, float64(1), pagination["page"], "Default page should be 1")
	assert.Equal(t, float64(20), pagination["limit"], "Default limit should be 20")
}

// TestPagination_Users_CustomParams teste la pagination avec des paramètres personnalisés pour users
func TestPagination_Users_CustomParams(t *testing.T) {
	router, _, _, cleanup := setupPaginationTestRouter(t)
	defer cleanup()

	req := httptest.NewRequest(http.MethodGet, "/api/v1/users?page=2&limit=5", nil)
	w := httptest.NewRecorder()
	router.ServeHTTP(w, req)

	assert.Equal(t, http.StatusOK, w.Code)

	var resp map[string]interface{}
	err := json.Unmarshal(w.Body.Bytes(), &resp)
	require.NoError(t, err)

	data, ok := resp["data"].(map[string]interface{})
	require.True(t, ok)

	users, ok := data["users"].([]interface{})
	require.True(t, ok)
	assert.LessOrEqual(t, len(users), 5, "Should return at most 5 users")

	pagination, ok := data["pagination"].(map[string]interface{})
	require.True(t, ok)
	assert.Equal(t, float64(2), pagination["page"], "Page should be 2")
	assert.Equal(t, float64(5), pagination["limit"], "Limit should be 5")
}

// TestPagination_Users_Metadata teste les métadonnées de pagination pour users
func TestPagination_Users_Metadata(t *testing.T) {
	router, _, _, cleanup := setupPaginationTestRouter(t)
	defer cleanup()

	req := httptest.NewRequest(http.MethodGet, "/api/v1/users?page=1&limit=5", nil)
	w := httptest.NewRecorder()
	router.ServeHTTP(w, req)

	assert.Equal(t, http.StatusOK, w.Code)

	var resp map[string]interface{}
	err := json.Unmarshal(w.Body.Bytes(), &resp)
	require.NoError(t, err)

	data, ok := resp["data"].(map[string]interface{})
	require.True(t, ok)

	pagination, ok := data["pagination"].(map[string]interface{})
	require.True(t, ok)

	// Verify pagination metadata
	assert.Contains(t, pagination, "page")
	assert.Contains(t, pagination, "limit")
	assert.Contains(t, pagination, "total")
	assert.Contains(t, pagination, "total_pages")
	assert.Contains(t, pagination, "has_next")
	assert.Contains(t, pagination, "has_prev")

	total := pagination["total"].(float64)
	assert.Greater(t, total, float64(0), "Total should be greater than 0")

	hasNext := pagination["has_next"].(bool)
	hasPrev := pagination["has_prev"].(bool)

	// On first page, should not have previous
	assert.False(t, hasPrev, "First page should not have previous")

	// If total > limit, should have next
	if total > pagination["limit"].(float64) {
		assert.True(t, hasNext, "Should have next page if total > limit")
	}
}

// TestPagination_Playlists_Default teste la pagination par défaut pour les playlists
func TestPagination_Playlists_Default(t *testing.T) {
	router, _, _, cleanup := setupPaginationTestRouter(t)
	defer cleanup()

	req := httptest.NewRequest(http.MethodGet, "/api/v1/playlists", nil)
	w := httptest.NewRecorder()
	router.ServeHTTP(w, req)

	assert.Equal(t, http.StatusOK, w.Code)

	var resp map[string]interface{}
	err := json.Unmarshal(w.Body.Bytes(), &resp)
	require.NoError(t, err)

	assert.True(t, resp["success"].(bool))

	data, ok := resp["data"].(map[string]interface{})
	require.True(t, ok)

	playlists, ok := data["playlists"].([]interface{})
	require.True(t, ok)
	assert.LessOrEqual(t, len(playlists), 20, "Default limit should be 20")

	// Playlists endpoint might return different structure
	// Check if pagination is present
	if pagination, ok := data["pagination"].(map[string]interface{}); ok {
		assert.Equal(t, float64(1), pagination["page"], "Default page should be 1")
		assert.Equal(t, float64(20), pagination["limit"], "Default limit should be 20")
	} else {
		// Some endpoints might return page/limit directly
		assert.Contains(t, data, "page")
		assert.Contains(t, data, "limit")
	}
}

// TestPagination_Playlists_CustomParams teste la pagination avec des paramètres personnalisés pour playlists
func TestPagination_Playlists_CustomParams(t *testing.T) {
	router, _, _, cleanup := setupPaginationTestRouter(t)
	defer cleanup()

	req := httptest.NewRequest(http.MethodGet, "/api/v1/playlists?page=2&limit=15", nil)
	w := httptest.NewRecorder()
	router.ServeHTTP(w, req)

	assert.Equal(t, http.StatusOK, w.Code)

	var resp map[string]interface{}
	err := json.Unmarshal(w.Body.Bytes(), &resp)
	require.NoError(t, err)

	data, ok := resp["data"].(map[string]interface{})
	require.True(t, ok)

	playlists, ok := data["playlists"].([]interface{})
	require.True(t, ok)
	assert.LessOrEqual(t, len(playlists), 15, "Should return at most 15 playlists")
}

// TestPagination_Consistency teste que tous les endpoints utilisent le même format de pagination
func TestPagination_Consistency(t *testing.T) {
	router, _, _, cleanup := setupPaginationTestRouter(t)
	defer cleanup()

	endpoints := []string{
		"/api/v1/tracks?page=1&limit=10",
		"/api/v1/users?page=1&limit=10",
		"/api/v1/playlists?page=1&limit=10",
	}

	for _, endpoint := range endpoints {
		t.Run(fmt.Sprintf("Consistency: %s", endpoint), func(t *testing.T) {
			req := httptest.NewRequest(http.MethodGet, endpoint, nil)
			w := httptest.NewRecorder()
			router.ServeHTTP(w, req)

			assert.Equal(t, http.StatusOK, w.Code)

			var resp map[string]interface{}
			err := json.Unmarshal(w.Body.Bytes(), &resp)
			require.NoError(t, err)

			// All endpoints should return success: true
			assert.True(t, resp["success"].(bool))

			// All endpoints should have data
			data, ok := resp["data"].(map[string]interface{})
			require.True(t, ok, "Response should have data field")

			// All endpoints should have pagination info (either in pagination object or directly)
			hasPagination := false
			if _, ok := data["pagination"]; ok {
				hasPagination = true
			} else if _, ok := data["page"]; ok {
				hasPagination = true
			}
			assert.True(t, hasPagination, "Response should have pagination information")
		})
	}
}

// TestPagination_EdgeCases teste les cas limites de pagination
func TestPagination_EdgeCases(t *testing.T) {
	router, _, _, cleanup := setupPaginationTestRouter(t)
	defer cleanup()

	tests := []struct {
		name           string
		query          string
		expectedStatus int
	}{
		{
			name:           "Empty query",
			query:          "",
			expectedStatus: http.StatusOK,
		},
		{
			name:           "Very large page number",
			query:          "page=999999&limit=10",
			expectedStatus: http.StatusOK,
		},
		{
			name:           "Page 1 with limit 1",
			query:          "page=1&limit=1",
			expectedStatus: http.StatusOK,
		},
		{
			name:           "Maximum limit",
			query:          "page=1&limit=100",
			expectedStatus: http.StatusOK,
		},
		{
			name:           "Limit exceeds maximum",
			query:          "page=1&limit=1000",
			expectedStatus: http.StatusOK,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			req := httptest.NewRequest(http.MethodGet, fmt.Sprintf("/api/v1/tracks?%s", tt.query), nil)
			w := httptest.NewRecorder()
			router.ServeHTTP(w, req)

			assert.Equal(t, tt.expectedStatus, w.Code, "Should handle edge case gracefully")

			if w.Code == http.StatusOK {
				var resp map[string]interface{}
				err := json.Unmarshal(w.Body.Bytes(), &resp)
				require.NoError(t, err)
				assert.True(t, resp["success"].(bool), "Response should be successful")
			}
		})
	}
}

// TestPagination_TotalCount teste que le total count est correct
func TestPagination_TotalCount(t *testing.T) {
	router, db, _, cleanup := setupPaginationTestRouter(t)
	defer cleanup()

	// Count total tracks in database
	var totalTracks int64
	db.Model(&models.Track{}).Count(&totalTracks)

	// Request first page
	req := httptest.NewRequest(http.MethodGet, "/api/v1/tracks?page=1&limit=10", nil)
	w := httptest.NewRecorder()
	router.ServeHTTP(w, req)

	assert.Equal(t, http.StatusOK, w.Code)

	var resp map[string]interface{}
	err := json.Unmarshal(w.Body.Bytes(), &resp)
	require.NoError(t, err)

	data, ok := resp["data"].(map[string]interface{})
	require.True(t, ok)

	pagination, ok := data["pagination"].(map[string]interface{})
	require.True(t, ok)

	// Verify total matches database count
	responseTotal := pagination["total"].(float64)
	assert.Equal(t, float64(totalTracks), responseTotal, "Total should match database count")
}

// TestPagination_TotalPages teste le calcul de total_pages
func TestPagination_TotalPages(t *testing.T) {
	router, db, _, cleanup := setupPaginationTestRouter(t)
	defer cleanup()

	// Count total tracks
	var totalTracks int64
	db.Model(&models.Track{}).Count(&totalTracks)

	limit := 10
	expectedTotalPages := int((totalTracks + int64(limit) - 1) / int64(limit))

	req := httptest.NewRequest(http.MethodGet, fmt.Sprintf("/api/v1/tracks?page=1&limit=%d", limit), nil)
	w := httptest.NewRecorder()
	router.ServeHTTP(w, req)

	assert.Equal(t, http.StatusOK, w.Code)

	var resp map[string]interface{}
	err := json.Unmarshal(w.Body.Bytes(), &resp)
	require.NoError(t, err)

	data, ok := resp["data"].(map[string]interface{})
	require.True(t, ok)

	pagination, ok := data["pagination"].(map[string]interface{})
	require.True(t, ok)

	totalPages := pagination["total_pages"].(float64)
	assert.Equal(t, float64(expectedTotalPages), totalPages, "Total pages should be calculated correctly")
}
