package handlers

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

	"github.com/google/uuid"

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

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

// setupPlaylistIntegrationTestRouter crée un router de test avec les handlers de playlists
// T0456: Create Playlist Integration Tests
func setupPlaylistIntegrationTestRouter(t *testing.T) (*gin.Engine, *gorm.DB, func()) {
	gin.SetMode(gin.TestMode)

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

	// Enable foreign keys for SQLite
	db.Exec("PRAGMA foreign_keys = ON")

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

	// Setup logger
	logger := zap.NewNop()

	// Setup service
	playlistService := services.NewPlaylistServiceWithDB(db, logger)
	playlistHandler := NewPlaylistHandler(playlistService, db, logger)

	// Create router
	router := gin.New()
	v1 := router.Group("/api/v1")
	{
		// Optional auth middleware for GET routes - sets user_id if present, but doesn't block
		optionalAuth := func(c *gin.Context) {
			if userIDStr := c.Query("user_id"); userIDStr != "" {
				if uid, err := uuid.Parse(userIDStr); err == nil {
					c.Set("user_id", uid)
				}
			} else if userIDStr := c.GetHeader("X-User-ID"); userIDStr != "" {
				if uid, err := uuid.Parse(userIDStr); err == nil {
					c.Set("user_id", uid)
				}
			}
			c.Next()
		}

		// Public routes - GET endpoints handle authorization internally
		// (they check if playlist is public or user is owner)
		v1.GET("/playlists", optionalAuth, playlistHandler.GetPlaylists)
		v1.GET("/playlists/:id", optionalAuth, playlistHandler.GetPlaylist)

		// Protected routes (simplified - no real auth middleware for integration tests)
		protected := v1.Group("/")
		protected.Use(func(c *gin.Context) {
			// Mock auth middleware - set user_id from query param or header
			// If no user_id provided, return 401 Unauthorized
			userIDSet := false
			if userIDStr := c.Query("user_id"); userIDStr != "" {
				uid, err := uuid.Parse(userIDStr)
				if err == nil {
					c.Set("user_id", uid)
					userIDSet = true
				}
			} else if userIDStr := c.GetHeader("X-User-ID"); userIDStr != "" {
				uid, err := uuid.Parse(userIDStr)
				if err == nil {
					c.Set("user_id", uid)
					userIDSet = true
				}
			}

			// If user_id not set, return 401 Unauthorized
			if !userIDSet {
				c.JSON(http.StatusUnauthorized, gin.H{"error": "unauthorized"})
				c.Abort()
				return
			}
			c.Next()
		})
		{
			protected.POST("/playlists", playlistHandler.CreatePlaylist)
			protected.PUT("/playlists/:id", playlistHandler.UpdatePlaylist)
			protected.DELETE("/playlists/:id", playlistHandler.DeletePlaylist)
		}
	}

	cleanup := func() {
		// Database will be closed automatically
	}

	return router, db, cleanup
}

// createTestUser crée un utilisateur de test
func createTestUserForPlaylist(t *testing.T, db *gorm.DB, userID uuid.UUID, username string) *models.User {
	timestamp := time.Now().UnixNano()
	uniqueUsername := fmt.Sprintf("%s_%d", username, timestamp)
	user := &models.User{
		ID:           userID,
		Username:     uniqueUsername,
		Slug:         uniqueUsername,
		Email:        fmt.Sprintf("%s@example.com", uniqueUsername),
		PasswordHash: "hashed_password",
		IsActive:     true,
		CreatedAt:    time.Now(),
	}
	err := db.Create(user).Error
	require.NoError(t, err)
	return user
}

// TestCreatePlaylist_Success teste la création réussie d'une playlist
// T0456: Create Playlist Integration Tests
func TestCreatePlaylist_Success(t *testing.T) {
	if testing.Short() {
		t.Skip("Skipping integration test in short mode")
	}

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

	// Créer un utilisateur de test
	userID := uuid.New()
	createTestUserForPlaylist(t, db, userID, "testuser")

	// Créer une playlist
	reqBody := map[string]interface{}{
		"title":       "My Awesome Playlist",
		"description": "A test playlist with great songs",
		"is_public":   true,
	}
	body, err := json.Marshal(reqBody)
	require.NoError(t, err)

	req := httptest.NewRequest("POST", fmt.Sprintf("/api/v1/playlists?user_id=%s", userID), bytes.NewBuffer(body))
	req.Header.Set("Content-Type", "application/json")
	w := httptest.NewRecorder()

	router.ServeHTTP(w, req)

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

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

	// Vérifier le format de réponse standardisé {success: true, data: {playlist: {...}}}
	assert.Contains(t, response, "data")
	data, ok := response["data"].(map[string]interface{})
	require.True(t, ok, "response should contain 'data' key with map value")
	assert.Contains(t, data, "playlist")
	playlistData, ok := data["playlist"].(map[string]interface{})
	require.True(t, ok, "data should contain 'playlist' key with map value")
	playlist := playlistData
	assert.Equal(t, "My Awesome Playlist", playlist["title"])
	assert.Equal(t, "A test playlist with great songs", playlist["description"])
	assert.Equal(t, true, playlist["is_public"])
	assert.Equal(t, userID.String(), playlist["user_id"])
}

// TestCreatePlaylist_ValidationErrors teste les erreurs de validation
// T0456: Create Playlist Integration Tests
func TestCreatePlaylist_ValidationErrors(t *testing.T) {
	if testing.Short() {
		t.Skip("Skipping integration test in short mode")
	}

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

	userID := uuid.New()
	createTestUserForPlaylist(t, db, userID, "testuser")

	tests := []struct {
		name          string
		reqBody       map[string]interface{}
		expectedCode  int
		errorContains string
	}{
		{
			name: "empty title",
			reqBody: map[string]interface{}{
				"title":     "",
				"is_public": true,
			},
			expectedCode:  http.StatusBadRequest,
			errorContains: "required",
		},
		{
			name: "title too long",
			reqBody: map[string]interface{}{
				"title":     string(make([]byte, 201)), // 201 characters
				"is_public": true,
			},
			expectedCode:  http.StatusBadRequest,
			errorContains: "200",
		},
		{
			name: "missing title",
			reqBody: map[string]interface{}{
				"description": "Some description",
				"is_public":   true,
			},
			expectedCode:  http.StatusBadRequest,
			errorContains: "required",
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			body, err := json.Marshal(tt.reqBody)
			require.NoError(t, err)

			req := httptest.NewRequest("POST", fmt.Sprintf("/api/v1/playlists?user_id=%s", userID), bytes.NewBuffer(body))
			req.Header.Set("Content-Type", "application/json")
			w := httptest.NewRecorder()

			router.ServeHTTP(w, req)

			assert.Equal(t, tt.expectedCode, w.Code)
			var response map[string]interface{}
			json.Unmarshal(w.Body.Bytes(), &response)
			if tt.errorContains != "" {
				assert.Contains(t, w.Body.String(), tt.errorContains)
			}
		})
	}
}

// TestCreatePlaylist_Unauthorized teste la création sans authentification
// T0456: Create Playlist Integration Tests
func TestCreatePlaylist_Unauthorized(t *testing.T) {
	if testing.Short() {
		t.Skip("Skipping integration test in short mode")
	}

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

	reqBody := map[string]interface{}{
		"title":     "My Playlist",
		"is_public": true,
	}
	body, _ := json.Marshal(reqBody)

	req := httptest.NewRequest("POST", "/api/v1/playlists", bytes.NewBuffer(body))
	req.Header.Set("Content-Type", "application/json")
	w := httptest.NewRecorder()

	router.ServeHTTP(w, req)

	// Le handler vérifie user_id, donc si pas d'auth, ça devrait échouer
	// Mais notre mock middleware ne set pas user_id si pas de query param
	assert.Equal(t, http.StatusUnauthorized, w.Code)
}

// TestGetPlaylist_Public teste la récupération d'une playlist publique
// T0456: Create Playlist Integration Tests
func TestGetPlaylist_Public(t *testing.T) {
	if testing.Short() {
		t.Skip("Skipping integration test in short mode")
	}

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

	// Créer un utilisateur et une playlist publique
	userID := uuid.New()
	createTestUserForPlaylist(t, db, userID, "testuser")

	playlist := &models.Playlist{
		UserID:   userID,
		Title:    "Public Playlist",
		IsPublic: true,
	}
	err := db.Create(playlist).Error
	require.NoError(t, err)

	// Récupérer la playlist sans authentification
	req := httptest.NewRequest("GET", fmt.Sprintf("/api/v1/playlists/%s", playlist.ID), nil)
	w := httptest.NewRecorder()

	router.ServeHTTP(w, req)

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

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

	// MOD-P0-002: Accéder au format de réponse standardisé {success: true, data: {playlist: {...}}}
	assert.Contains(t, response, "data")
	data, ok := response["data"].(map[string]interface{})
	require.True(t, ok, "response should contain 'data' key with map value")
	assert.Contains(t, data, "playlist")
	playlistData, ok := data["playlist"].(map[string]interface{})
	require.True(t, ok, "data should contain 'playlist' key with map value")
	assert.Equal(t, "Public Playlist", playlistData["title"])
	assert.Equal(t, true, playlistData["is_public"])
}

// TestGetPlaylist_Private_Unauthorized teste l'accès à une playlist privée sans auth
// T0456: Create Playlist Integration Tests
func TestGetPlaylist_Private_Unauthorized(t *testing.T) {
	if testing.Short() {
		t.Skip("Skipping integration test in short mode")
	}

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

	// Créer un utilisateur et une playlist privée
	userID := uuid.New()
	createTestUserForPlaylist(t, db, userID, "testuser")

	playlist := &models.Playlist{
		UserID:   userID,
		Title:    "Private Playlist",
		IsPublic: false,
	}
	err := db.Create(playlist).Error
	require.NoError(t, err)

	// Force IsPublic to false (GORM might use default value true)
	err = db.Model(playlist).Update("is_public", false).Error
	require.NoError(t, err)

	// Vérifier que la playlist est bien privée
	var createdPlaylist models.Playlist
	err = db.First(&createdPlaylist, playlist.ID).Error
	require.NoError(t, err)
	require.False(t, createdPlaylist.IsPublic, "Playlist should be private")

	// Essayer de récupérer la playlist sans authentification
	req := httptest.NewRequest("GET", fmt.Sprintf("/api/v1/playlists/%s", playlist.ID), nil)
	w := httptest.NewRecorder()

	router.ServeHTTP(w, req)

	// Devrait retourner 404 (Not Found) car le service retourne ErrPlaylistNotFound pour les playlists privées
	// sans authentification (sécurité : ne pas révéler l'existence de playlists privées)
	assert.Equal(t, http.StatusNotFound, w.Code)
}

// TestGetPlaylist_Private_AsOwner teste l'accès à une playlist privée en tant que propriétaire
// T0456: Create Playlist Integration Tests
func TestGetPlaylist_Private_AsOwner(t *testing.T) {
	if testing.Short() {
		t.Skip("Skipping integration test in short mode")
	}

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

	// Créer un utilisateur et une playlist privée
	userID := uuid.New()
	createTestUserForPlaylist(t, db, userID, "testuser")

	playlist := &models.Playlist{
		UserID:   userID,
		Title:    "Private Playlist",
		IsPublic: false,
	}
	err := db.Create(playlist).Error
	require.NoError(t, err)

	// Récupérer la playlist en tant que propriétaire
	req := httptest.NewRequest("GET", fmt.Sprintf("/api/v1/playlists/%s?user_id=%s", playlist.ID, userID), nil)
	w := httptest.NewRecorder()

	router.ServeHTTP(w, req)

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

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

	// MOD-P0-002: Accéder au format de réponse standardisé {success: true, data: {playlist: {...}}}
	assert.Contains(t, response, "data")
	data, ok := response["data"].(map[string]interface{})
	require.True(t, ok, "response should contain 'data' key with map value")
	assert.Contains(t, data, "playlist")
	playlistData, ok := data["playlist"].(map[string]interface{})
	require.True(t, ok, "data should contain 'playlist' key with map value")
	assert.Equal(t, "Private Playlist", playlistData["title"])
}

// TestUpdatePlaylist_AsOwner teste la mise à jour d'une playlist en tant que propriétaire
// T0456: Create Playlist Integration Tests
func TestUpdatePlaylist_AsOwner(t *testing.T) {
	if testing.Short() {
		t.Skip("Skipping integration test in short mode")
	}

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

	// Créer un utilisateur et une playlist
	userID := uuid.New()
	createTestUserForPlaylist(t, db, userID, "testuser")

	playlist := &models.Playlist{
		UserID:      userID,
		Title:       "Original Title",
		Description: "Original description",
		IsPublic:    true,
	}
	err := db.Create(playlist).Error
	require.NoError(t, err)

	// Mettre à jour la playlist
	newTitle := "Updated Title"
	newDescription := "Updated description"
	newIsPublic := false
	reqBody := map[string]interface{}{
		"title":       newTitle,
		"description": newDescription,
		"is_public":   newIsPublic,
	}
	body, err := json.Marshal(reqBody)
	require.NoError(t, err)

	req := httptest.NewRequest("PUT", fmt.Sprintf("/api/v1/playlists/%s?user_id=%s", playlist.ID, userID), bytes.NewBuffer(body))
	req.Header.Set("Content-Type", "application/json")
	w := httptest.NewRecorder()

	router.ServeHTTP(w, req)

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

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

	// MOD-P0-002: Accéder au format de réponse standardisé {success: true, data: {playlist: {...}}}
	assert.Contains(t, response, "data")
	data, ok := response["data"].(map[string]interface{})
	require.True(t, ok, "response should contain 'data' key with map value")
	assert.Contains(t, data, "playlist")
	playlistData, ok := data["playlist"].(map[string]interface{})
	require.True(t, ok, "data should contain 'playlist' key with map value")
	assert.Equal(t, newTitle, playlistData["title"])
	assert.Equal(t, newDescription, playlistData["description"])
	assert.Equal(t, newIsPublic, playlistData["is_public"])
}

// TestUpdatePlaylist_NotOwner teste la mise à jour d'une playlist par un non-propriétaire
// T0456: Create Playlist Integration Tests
func TestUpdatePlaylist_NotOwner(t *testing.T) {
	if testing.Short() {
		t.Skip("Skipping integration test in short mode")
	}

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

	// Créer deux utilisateurs
	user1ID := uuid.New()
	user2ID := uuid.New()
	createTestUserForPlaylist(t, db, user1ID, "user1")
	createTestUserForPlaylist(t, db, user2ID, "user2")

	// Créer une playlist pour user1
	playlist := &models.Playlist{
		UserID:   user1ID,
		Title:    "User1's Playlist",
		IsPublic: true,
	}
	err := db.Create(playlist).Error
	require.NoError(t, err)

	// Essayer de mettre à jour en tant que user2
	reqBody := map[string]interface{}{
		"title": "Hacked Title",
	}
	body, err := json.Marshal(reqBody)
	require.NoError(t, err)

	req := httptest.NewRequest("PUT", fmt.Sprintf("/api/v1/playlists/%s?user_id=%s", playlist.ID, user2ID), bytes.NewBuffer(body))
	req.Header.Set("Content-Type", "application/json")
	w := httptest.NewRecorder()

	router.ServeHTTP(w, req)

	// Devrait retourner 403 Forbidden
	assert.Equal(t, http.StatusForbidden, w.Code)
}

// TestDeletePlaylist_AsOwner teste la suppression d'une playlist en tant que propriétaire
// T0456: Create Playlist Integration Tests
func TestDeletePlaylist_AsOwner(t *testing.T) {
	if testing.Short() {
		t.Skip("Skipping integration test in short mode")
	}

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

	// Créer un utilisateur et une playlist
	userID := uuid.New()
	createTestUserForPlaylist(t, db, userID, "testuser")

	playlist := &models.Playlist{
		UserID:   userID,
		Title:    "Playlist to Delete",
		IsPublic: true,
	}
	err := db.Create(playlist).Error
	require.NoError(t, err)

	// Supprimer la playlist
	req := httptest.NewRequest("DELETE", fmt.Sprintf("/api/v1/playlists/%s?user_id=%s", playlist.ID, userID), nil)
	w := httptest.NewRecorder()

	router.ServeHTTP(w, req)

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

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

	// MOD-P0-002: Accéder au format de réponse standardisé {success: true, data: {message: "..."}}
	assert.Contains(t, response, "data")
	data, ok := response["data"].(map[string]interface{})
	require.True(t, ok, "response should contain 'data' key with map value")
	assert.Contains(t, data, "message")
	assert.Equal(t, "playlist deleted", data["message"])

	// Vérifier que la playlist est bien supprimée
	var count int64
	db.Model(&models.Playlist{}).Where("id = ?", playlist.ID).Count(&count)
	assert.Equal(t, int64(0), count)
}

// TestDeletePlaylist_NotOwner teste la suppression d'une playlist par un non-propriétaire
// T0456: Create Playlist Integration Tests
func TestDeletePlaylist_NotOwner(t *testing.T) {
	if testing.Short() {
		t.Skip("Skipping integration test in short mode")
	}

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

	// Créer deux utilisateurs
	user1ID := uuid.New()
	user2ID := uuid.New()
	createTestUserForPlaylist(t, db, user1ID, "user1")
	createTestUserForPlaylist(t, db, user2ID, "user2")

	// Créer une playlist pour user1
	playlist := &models.Playlist{
		UserID:   user1ID,
		Title:    "User1's Playlist",
		IsPublic: true,
	}
	err := db.Create(playlist).Error
	require.NoError(t, err)

	// Essayer de supprimer en tant que user2
	req := httptest.NewRequest("DELETE", fmt.Sprintf("/api/v1/playlists/%s?user_id=%s", playlist.ID, user2ID), nil)
	w := httptest.NewRecorder()

	router.ServeHTTP(w, req)

	// Devrait retourner 403 Forbidden
	assert.Equal(t, http.StatusForbidden, w.Code)
}

// TestListPlaylists_Pagination teste la pagination des playlists
// T0456: Create Playlist Integration Tests
func TestListPlaylists_Pagination(t *testing.T) {
	if testing.Short() {
		t.Skip("Skipping integration test in short mode")
	}

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

	// Créer un utilisateur
	userID := uuid.New()
	createTestUserForPlaylist(t, db, userID, "testuser")

	// Créer plusieurs playlists
	for i := 0; i < 5; i++ {
		playlist := &models.Playlist{
			UserID:   userID,
			Title:    fmt.Sprintf("Playlist %d", i+1),
			IsPublic: true,
		}
		err := db.Create(playlist).Error
		require.NoError(t, err)
	}

	// Récupérer la première page (limit=2)
	req := httptest.NewRequest("GET", "/api/v1/playlists?page=1&limit=2", nil)
	w := httptest.NewRecorder()

	router.ServeHTTP(w, req)

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

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

	// MOD-P0-002: Accéder au format de réponse standardisé {success: true, data: {playlists: [...], total: ..., page: ...}}
	assert.Contains(t, response, "data")
	data, ok := response["data"].(map[string]interface{})
	require.True(t, ok, "response should contain 'data' key with map value")
	assert.Contains(t, data, "playlists")
	assert.Contains(t, data, "total")
	assert.Contains(t, data, "page")
	assert.Contains(t, data, "limit")
	playlists := data["playlists"].([]interface{})
	assert.LessOrEqual(t, len(playlists), 2)
	assert.Equal(t, float64(5), data["total"])
	assert.Equal(t, float64(1), data["page"])
	assert.Equal(t, float64(2), data["limit"])
}

// TestListPlaylists_FilterByUser teste le filtrage par utilisateur
// T0456: Create Playlist Integration Tests
func TestListPlaylists_FilterByUser(t *testing.T) {
	if testing.Short() {
		t.Skip("Skipping integration test in short mode")
	}

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

	// Créer deux utilisateurs
	user1ID := uuid.New()
	user2ID := uuid.New()
	createTestUserForPlaylist(t, db, user1ID, "user1")
	createTestUserForPlaylist(t, db, user2ID, "user2")

	// Créer des playlists pour chaque utilisateur
	for i := 0; i < 3; i++ {
		playlist := &models.Playlist{
			UserID:   user1ID,
			Title:    fmt.Sprintf("User1 Playlist %d", i+1),
			IsPublic: true,
		}
		err := db.Create(playlist).Error
		require.NoError(t, err)
	}

	for i := 0; i < 2; i++ {
		playlist := &models.Playlist{
			UserID:   user2ID,
			Title:    fmt.Sprintf("User2 Playlist %d", i+1),
			IsPublic: true,
		}
		err := db.Create(playlist).Error
		require.NoError(t, err)
	}

	// Filtrer par user1
	req := httptest.NewRequest("GET", fmt.Sprintf("/api/v1/playlists?user_id=%s", user1ID), nil)
	w := httptest.NewRecorder()

	router.ServeHTTP(w, req)

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

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

	// MOD-P0-002: Accéder au format de réponse standardisé {success: true, data: {playlists: [...], total: ...}}
	assert.Contains(t, response, "data")
	data2, ok := response["data"].(map[string]interface{})
	require.True(t, ok, "response should contain 'data' key with map value")
	playlists := data2["playlists"].([]interface{})
	assert.Equal(t, 3, len(playlists))
	assert.Equal(t, float64(3), data2["total"])

	// Vérifier que toutes les playlists appartiennent à user1
	for _, p := range playlists {
		playlistData := p.(map[string]interface{})
		assert.Equal(t, user1ID.String(), playlistData["user_id"])
	}
}
