package handlers

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

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

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

// setupPlaylistTrackIntegrationTestRouter crée un router de test avec les handlers de playlist tracks
// T0468: Create PlaylistTrack Integration Tests
func setupPlaylistTrackIntegrationTestRouter(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)

	// Setup handler
	playlistHandler := NewPlaylistHandler(playlistService, db, logger)

	// Create router
	router := gin.New()
	v1 := router.Group("/api/v1")
	{
		// 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 != "" {
				if uid, err := uuid.Parse(userIDStr); err == nil {
					c.Set("user_id", uid)
					userIDSet = true
				}
			} else if userIDStr := c.GetHeader("X-User-ID"); userIDStr != "" {
				if uid, err := uuid.Parse(userIDStr); 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()
		})
		{
			// T0468: Routes pour gestion des tracks dans les playlists
			protected.POST("/playlists/:id/tracks/:trackId", playlistHandler.AddTrack)
			protected.DELETE("/playlists/:id/tracks/:trackId", playlistHandler.RemoveTrack)
			protected.PUT("/playlists/:id/tracks/reorder", playlistHandler.ReorderTracks)
		}
	}

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

	return router, db, cleanup
}

// createTestTrackForPlaylist crée un track de test
func createTestTrackForPlaylist(t *testing.T, db *gorm.DB, userID uuid.UUID, title string) *models.Track {
	timestamp := time.Now().UnixNano()
	track := &models.Track{
		UserID:    userID,
		Title:     fmt.Sprintf("%s_%d", title, timestamp),
		Artist:    "Test Artist",
		Duration:  180,
		FilePath:  fmt.Sprintf("/test/track_%d.mp3", timestamp),
		FileSize:  5 * 1024 * 1024,
		Format:    "MP3",
		IsPublic:  true,
		Status:    models.TrackStatusCompleted,
		CreatedAt: time.Now(),
		UpdatedAt: time.Now(),
	}
	err := db.Create(track).Error
	require.NoError(t, err)
	return track
}

// TestAddTrackToPlaylist_Success teste l'ajout réussi d'un track à une playlist
// T0468: Create PlaylistTrack Integration Tests
func TestAddTrackToPlaylist_Success(t *testing.T) {
	if testing.Short() {
		t.Skip("Skipping integration test in short mode")
	}

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

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

	// Créer une playlist
	playlist := &models.Playlist{
		UserID:   userID,
		Title:    "My Playlist",
		IsPublic: true,
	}
	err := db.Create(playlist).Error
	require.NoError(t, err)

	// Créer un track
	track := createTestTrackForPlaylist(t, db, userID, "Test Track")

	// Ajouter le track à la playlist via l'URL params
	req := httptest.NewRequest("POST", fmt.Sprintf("/api/v1/playlists/%s/tracks/%s?user_id=%s", playlist.ID, track.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)

	// RespondSuccess returns {"success": true, "data": {...}}
	assert.True(t, response["success"].(bool))
	data, ok := response["data"].(map[string]interface{})
	require.True(t, ok, "Response should have data field")
	assert.Contains(t, data, "message")
	assert.Equal(t, "track added to playlist", data["message"])

	// Vérifier que le track a été ajouté
	var playlistTrack models.PlaylistTrack
	err = db.Where("playlist_id = ? AND track_id = ?", playlist.ID, track.ID).First(&playlistTrack).Error
	assert.NoError(t, err)
	assert.Equal(t, playlist.ID, playlistTrack.PlaylistID)
	assert.Equal(t, track.ID, playlistTrack.TrackID)

	// Vérifier que le track_count a été mis à jour
	var updatedPlaylist models.Playlist
	err = db.First(&updatedPlaylist, playlist.ID).Error
	require.NoError(t, err)
	assert.Equal(t, 1, updatedPlaylist.TrackCount)
}

// TestAddTrackToPlaylist_Ownership teste que seul le propriétaire peut ajouter un track
// T0468: Create PlaylistTrack Integration Tests
func TestAddTrackToPlaylist_Ownership(t *testing.T) {
	if testing.Short() {
		t.Skip("Skipping integration test in short mode")
	}

	router, db, cleanup := setupPlaylistTrackIntegrationTestRouter(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)

	// Créer un track pour user2
	track := createTestTrackForPlaylist(t, db, user2ID, "User2's Track")

	// Essayer d'ajouter le track en tant que user2 (non propriétaire)
	req := httptest.NewRequest("POST", fmt.Sprintf("/api/v1/playlists/%s/tracks/%s?user_id=%s", playlist.ID, track.ID, user2ID), nil)
	w := httptest.NewRecorder()

	router.ServeHTTP(w, req)

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

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

	// AppError format: {"success": false, "error": {"code": 1003, "message": "forbidden", ...}}
	assert.False(t, response["success"].(bool))
	errorData, ok := response["error"].(map[string]interface{})
	require.True(t, ok, "Response should have error field with AppError format")
	assert.Equal(t, "forbidden", errorData["message"])
}

// TestAddTrackToPlaylist_Unauthorized teste l'ajout sans authentification
// T0468: Create PlaylistTrack Integration Tests
func TestAddTrackToPlaylist_Unauthorized(t *testing.T) {
	if testing.Short() {
		t.Skip("Skipping integration test in short mode")
	}

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

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

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

	track := createTestTrackForPlaylist(t, db, userID, "Test Track")

	// Essayer d'ajouter sans authentification
	req := httptest.NewRequest("POST", fmt.Sprintf("/api/v1/playlists/%s/tracks/%s", playlist.ID, track.ID), nil)
	w := httptest.NewRecorder()

	router.ServeHTTP(w, req)

	// Devrait retourner 401 Unauthorized
	assert.Equal(t, http.StatusUnauthorized, w.Code)
}

// TestAddTrackToPlaylist_TrackNotFound teste l'ajout d'un track inexistant
// T0468: Create PlaylistTrack Integration Tests
func TestAddTrackToPlaylist_TrackNotFound(t *testing.T) {
	if testing.Short() {
		t.Skip("Skipping integration test in short mode")
	}

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

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

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

	// Essayer d'ajouter un track inexistant
	req := httptest.NewRequest("POST", fmt.Sprintf("/api/v1/playlists/%s/tracks/%s?user_id=%s", playlist.ID, uuid.New(), userID), nil)
	w := httptest.NewRecorder()

	router.ServeHTTP(w, req)

	// Devrait retourner 404 Not Found
	assert.Equal(t, http.StatusNotFound, w.Code)
}

// TestRemoveTrackFromPlaylist_Success teste la suppression réussie d'un track
// T0468: Create PlaylistTrack Integration Tests
func TestRemoveTrackFromPlaylist_Success(t *testing.T) {
	if testing.Short() {
		t.Skip("Skipping integration test in short mode")
	}

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

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

	// Créer une playlist
	playlist := &models.Playlist{
		UserID:   userID,
		Title:    "My Playlist",
		IsPublic: true,
	}
	err := db.Create(playlist).Error
	require.NoError(t, err)

	// Créer deux tracks
	track1 := createTestTrackForPlaylist(t, db, userID, "Track 1")
	track2 := createTestTrackForPlaylist(t, db, userID, "Track 2")

	// Ajouter les tracks à la playlist via le service
	playlistService := services.NewPlaylistServiceWithDB(db, zap.NewNop())
	err = playlistService.AddTrack(context.Background(), playlist.ID, track1.ID, userID)
	require.NoError(t, err)
	err = playlistService.AddTrack(context.Background(), playlist.ID, track2.ID, userID)
	require.NoError(t, err)

	// Retirer le premier track
	req := httptest.NewRequest("DELETE", fmt.Sprintf("/api/v1/playlists/%s/tracks/%s?user_id=%s", playlist.ID, track1.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)

	// RespondSuccess returns {"success": true, "data": {...}}
	assert.True(t, response["success"].(bool))
	data, ok := response["data"].(map[string]interface{})
	require.True(t, ok, "Response should have data field")
	assert.Contains(t, data, "message")
	assert.Equal(t, "track removed from playlist", data["message"])

	// Vérifier que le track a été retiré
	var count int64
	db.Model(&models.PlaylistTrack{}).Where("playlist_id = ? AND track_id = ?", playlist.ID, track1.ID).Count(&count)
	assert.Equal(t, int64(0), count)

	// Vérifier que le track_count a été mis à jour
	var updatedPlaylist models.Playlist
	err = db.First(&updatedPlaylist, playlist.ID).Error
	require.NoError(t, err)
	assert.Equal(t, 1, updatedPlaylist.TrackCount)
}

// TestRemoveTrackFromPlaylist_Ownership teste que seul le propriétaire peut retirer un track
// T0468: Create PlaylistTrack Integration Tests
func TestRemoveTrackFromPlaylist_Ownership(t *testing.T) {
	if testing.Short() {
		t.Skip("Skipping integration test in short mode")
	}

	router, db, cleanup := setupPlaylistTrackIntegrationTestRouter(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)

	// Créer un track et l'ajouter à la playlist
	track := createTestTrackForPlaylist(t, db, user1ID, "Track")
	playlistService := services.NewPlaylistServiceWithDB(db, zap.NewNop())
	err = playlistService.AddTrack(context.Background(), playlist.ID, track.ID, user1ID)
	require.NoError(t, err)

	// Essayer de retirer le track en tant que user2 (non propriétaire)
	req := httptest.NewRequest("DELETE", fmt.Sprintf("/api/v1/playlists/%s/tracks/%s?user_id=%s", playlist.ID, track.ID, user2ID), nil)
	w := httptest.NewRecorder()

	router.ServeHTTP(w, req)

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

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

	// AppError format: {"success": false, "error": {"code": 1003, "message": "forbidden", ...}}
	assert.False(t, response["success"].(bool))
	errorData, ok := response["error"].(map[string]interface{})
	require.True(t, ok, "Response should have error field with AppError format")
	assert.Equal(t, "forbidden", errorData["message"])
}

// TestReorderPlaylistTracks_Success teste la réorganisation réussie des tracks
// T0468: Create PlaylistTrack Integration Tests
func TestReorderPlaylistTracks_Success(t *testing.T) {
	if testing.Short() {
		t.Skip("Skipping integration test in short mode")
	}

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

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

	// Créer une playlist
	playlist := &models.Playlist{
		UserID:   userID,
		Title:    "My Playlist",
		IsPublic: true,
	}
	err := db.Create(playlist).Error
	require.NoError(t, err)

	// Créer trois tracks
	track1 := createTestTrackForPlaylist(t, db, userID, "Track 1")
	track2 := createTestTrackForPlaylist(t, db, userID, "Track 2")
	track3 := createTestTrackForPlaylist(t, db, userID, "Track 3")

	// Ajouter les tracks à la playlist via le service
	playlistService := services.NewPlaylistServiceWithDB(db, zap.NewNop())
	err = playlistService.AddTrack(context.Background(), playlist.ID, track1.ID, userID)
	require.NoError(t, err)
	err = playlistService.AddTrack(context.Background(), playlist.ID, track2.ID, userID)
	require.NoError(t, err)
	err = playlistService.AddTrack(context.Background(), playlist.ID, track3.ID, userID)
	require.NoError(t, err)

	// Réorganiser les tracks (ordre inverse)
	reqBody := map[string]interface{}{
		"track_ids": []uuid.UUID{track3.ID, track2.ID, track1.ID},
	}
	body, err := json.Marshal(reqBody)
	require.NoError(t, err)

	req := httptest.NewRequest("PUT", fmt.Sprintf("/api/v1/playlists/%s/tracks/reorder?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)

	// RespondSuccess returns {"success": true, "data": {...}}
	assert.True(t, response["success"].(bool))
	data, ok := response["data"].(map[string]interface{})
	require.True(t, ok, "Response should have data field")
	assert.Contains(t, data, "message")
	assert.Equal(t, "tracks reordered", data["message"])

	// Vérifier que les positions ont été mises à jour
	var tracks []models.PlaylistTrack
	err = db.Where("playlist_id = ?", playlist.ID).Order("position asc").Find(&tracks).Error
	assert.NoError(t, err)
	assert.Equal(t, 3, len(tracks))
	assert.Equal(t, track3.ID, tracks[0].TrackID)
	assert.Equal(t, track2.ID, tracks[1].TrackID)
	assert.Equal(t, track1.ID, tracks[2].TrackID)
}

// TestReorderPlaylistTracks_Ownership teste que seul le propriétaire peut réorganiser
// T0468: Create PlaylistTrack Integration Tests
func TestReorderPlaylistTracks_Ownership(t *testing.T) {
	if testing.Short() {
		t.Skip("Skipping integration test in short mode")
	}

	router, db, cleanup := setupPlaylistTrackIntegrationTestRouter(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)

	// Créer un track et l'ajouter à la playlist
	track := createTestTrackForPlaylist(t, db, user1ID, "Track")
	playlistService := services.NewPlaylistServiceWithDB(db, zap.NewNop())
	err = playlistService.AddTrack(context.Background(), playlist.ID, track.ID, user1ID)
	require.NoError(t, err)

	// Essayer de réorganiser en tant que user2 (non propriétaire)
	reqBody := map[string]interface{}{
		"track_ids": []uuid.UUID{track.ID},
	}
	body, err := json.Marshal(reqBody)
	require.NoError(t, err)

	req := httptest.NewRequest("PUT", fmt.Sprintf("/api/v1/playlists/%s/tracks/reorder?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)

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

	// AppError format: {"success": false, "error": {"code": 1003, "message": "forbidden", ...}}
	assert.False(t, response["success"].(bool))
	errorData, ok := response["error"].(map[string]interface{})
	require.True(t, ok, "Response should have error field with AppError format")
	assert.Equal(t, "forbidden", errorData["message"])
}

// TestReorderPlaylistTracks_InvalidRequest teste une requête invalide
// T0468: Create PlaylistTrack Integration Tests
func TestReorderPlaylistTracks_InvalidRequest(t *testing.T) {
	if testing.Short() {
		t.Skip("Skipping integration test in short mode")
	}

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

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

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

	// Essayer de réorganiser avec une requête invalide (pas de track_ids)
	reqBody := map[string]interface{}{}
	body, err := json.Marshal(reqBody)
	require.NoError(t, err)

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

	router.ServeHTTP(w, req)

	// Devrait retourner 400 Bad Request
	assert.Equal(t, http.StatusBadRequest, w.Code)
}
