package handlers

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

	"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"
	"veza-backend-api/internal/models"
	"veza-backend-api/internal/services"
)

// setupPlaylistCollaborationIntegrationTestRouter crée un router de test avec tous les handlers nécessaires
func setupPlaylistCollaborationIntegrationTestRouter(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 all models
	err = db.AutoMigrate(
		&models.User{},
		&models.Playlist{},
		&models.PlaylistTrack{},
		&models.PlaylistCollaborator{},
	)
	require.NoError(t, err)

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

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

	// Setup router
	router := gin.New()
	router.Use(func(c *gin.Context) {
		// Mock authentication middleware - set user_id from query param
		if userID := c.Query("user_id"); userID != "" {
			var uid int64
			_, err := fmt.Sscanf(userID, "%d", &uid)
			if err == nil {
				c.Set("user_id", uid)
			}
		}
		c.Next()
	})

	// Setup routes
	v1 := router.Group("/api/v1")
	{
		v1.POST("/playlists/:id/collaborators", playlistHandler.AddCollaborator)
		v1.GET("/playlists/:id/collaborators", playlistHandler.GetCollaborators)
		v1.DELETE("/playlists/:id/collaborators/:userId", playlistHandler.RemoveCollaborator)
		v1.PUT("/playlists/:id/collaborators/:userId", playlistHandler.UpdateCollaboratorPermission)
	}

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

	return router, db, cleanup
}

// createTestUser crée un utilisateur de test
func createTestUserForCollaboration(t *testing.T, db *gorm.DB, userID int64, username string) *models.User {
	user := &models.User{
		ID:           userID,
		Username:     username,
		Email:        username + "@example.com",
		PasswordHash: "hashed_password",
		Slug:         username,
		IsActive:     true,
		CreatedAt:    time.Now(),
	}
	err := db.Create(user).Error
	require.NoError(t, err)
	return user
}

// createTestPlaylist crée une playlist de test
func createTestPlaylistForCollaboration(t *testing.T, db *gorm.DB, userID int64, playlistID int64) *models.Playlist {
	playlist := &models.Playlist{
		ID:          playlistID,
		UserID:      userID,
		Title:       "Test Playlist",
		Description: "Test Description",
		IsPublic:    true,
		TrackCount:  0,
		CreatedAt:   time.Now(),
		UpdatedAt:   time.Now(),
	}
	err := db.Create(playlist).Error
	require.NoError(t, err)
	return playlist
}

// TestPlaylistCollaborationIntegration_AddCollaborator teste l'ajout d'un collaborateur
func TestPlaylistCollaborationIntegration_AddCollaborator(t *testing.T) {
	if testing.Short() {
		t.Skip("Skipping integration test in short mode")
	}

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

	// Créer des utilisateurs de test
	ownerID := int64(1)
	collaboratorID := int64(2)
	createTestUserForCollaboration(t, db, ownerID, "owner")
	createTestUserForCollaboration(t, db, collaboratorID, "collaborator")

	// Créer une playlist
	playlistID := int64(1)
	createTestPlaylistForCollaboration(t, db, ownerID, playlistID)

	// Test 1: Ajouter un collaborateur avec permission read
	reqBody := AddCollaboratorRequest{
		UserID:     collaboratorID,
		Permission: "read",
	}
	body, err := json.Marshal(reqBody)
	require.NoError(t, err)

	req := httptest.NewRequest("POST", fmt.Sprintf("/api/v1/playlists/%d/collaborators?user_id=%d", playlistID, ownerID), 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)
	assert.NotNil(t, response["collaborator"])

	// Vérifier que le collaborateur a été créé dans la base de données
	var collaborator models.PlaylistCollaborator
	err = db.Where("playlist_id = ? AND user_id = ?", playlistID, collaboratorID).First(&collaborator).Error
	require.NoError(t, err)
	assert.Equal(t, models.PlaylistPermissionRead, collaborator.Permission)

	// Test 2: Essayer d'ajouter le même collaborateur (devrait échouer)
	req = httptest.NewRequest("POST", fmt.Sprintf("/api/v1/playlists/%d/collaborators?user_id=%d", playlistID, ownerID), bytes.NewBuffer(body))
	req.Header.Set("Content-Type", "application/json")
	w = httptest.NewRecorder()
	router.ServeHTTP(w, req)

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

	// Test 3: Essayer d'ajouter un collaborateur sans être propriétaire (devrait échouer)
	otherUserID := int64(3)
	createTestUserForCollaboration(t, db, otherUserID, "other_user")
	req = httptest.NewRequest("POST", fmt.Sprintf("/api/v1/playlists/%d/collaborators?user_id=%d", playlistID, otherUserID), bytes.NewBuffer(body))
	req.Header.Set("Content-Type", "application/json")
	w = httptest.NewRecorder()
	router.ServeHTTP(w, req)

	assert.Equal(t, http.StatusForbidden, w.Code)
}

// TestPlaylistCollaborationIntegration_RemoveCollaborator teste la suppression d'un collaborateur
func TestPlaylistCollaborationIntegration_RemoveCollaborator(t *testing.T) {
	if testing.Short() {
		t.Skip("Skipping integration test in short mode")
	}

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

	// Créer des utilisateurs de test
	ownerID := int64(1)
	collaboratorID := int64(2)
	createTestUserForCollaboration(t, db, ownerID, "owner")
	createTestUserForCollaboration(t, db, collaboratorID, "collaborator")

	// Créer une playlist
	playlistID := int64(1)
	createTestPlaylistForCollaboration(t, db, ownerID, playlistID)

	// Ajouter un collaborateur via le service directement
	playlistService := services.NewPlaylistServiceWithDB(db, zap.NewNop())
	_, err := playlistService.AddCollaborator(nil, playlistID, ownerID, collaboratorID, models.PlaylistPermissionRead)
	require.NoError(t, err)

	// Test 1: Retirer le collaborateur
	req := httptest.NewRequest("DELETE", fmt.Sprintf("/api/v1/playlists/%d/collaborators/%d?user_id=%d", playlistID, collaboratorID, ownerID), 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)
	assert.Equal(t, "collaborator removed", response["message"])

	// Vérifier que le collaborateur a été supprimé
	var count int64
	db.Model(&models.PlaylistCollaborator{}).Where("playlist_id = ? AND user_id = ?", playlistID, collaboratorID).Count(&count)
	assert.Equal(t, int64(0), count)

	// Test 2: Essayer de retirer un collaborateur inexistant (devrait échouer)
	req = httptest.NewRequest("DELETE", fmt.Sprintf("/api/v1/playlists/%d/collaborators/%d?user_id=%d", playlistID, collaboratorID, ownerID), nil)
	w = httptest.NewRecorder()
	router.ServeHTTP(w, req)

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

	// Test 3: Essayer de retirer un collaborateur sans être propriétaire (devrait échouer)
	// Réajouter le collaborateur
	_, err = playlistService.AddCollaborator(nil, playlistID, ownerID, collaboratorID, models.PlaylistPermissionRead)
	require.NoError(t, err)

	otherUserID := int64(3)
	createTestUserForCollaboration(t, db, otherUserID, "other_user")
	req = httptest.NewRequest("DELETE", fmt.Sprintf("/api/v1/playlists/%d/collaborators/%d?user_id=%d", playlistID, collaboratorID, otherUserID), nil)
	w = httptest.NewRecorder()
	router.ServeHTTP(w, req)

	assert.Equal(t, http.StatusForbidden, w.Code)
}

// TestPlaylistCollaborationIntegration_UpdatePermission teste la mise à jour de la permission d'un collaborateur
func TestPlaylistCollaborationIntegration_UpdatePermission(t *testing.T) {
	if testing.Short() {
		t.Skip("Skipping integration test in short mode")
	}

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

	// Créer des utilisateurs de test
	ownerID := int64(1)
	collaboratorID := int64(2)
	createTestUserForCollaboration(t, db, ownerID, "owner")
	createTestUserForCollaboration(t, db, collaboratorID, "collaborator")

	// Créer une playlist
	playlistID := int64(1)
	createTestPlaylistForCollaboration(t, db, ownerID, playlistID)

	// Ajouter un collaborateur avec permission read
	playlistService := services.NewPlaylistServiceWithDB(db, zap.NewNop())
	_, err := playlistService.AddCollaborator(nil, playlistID, ownerID, collaboratorID, models.PlaylistPermissionRead)
	require.NoError(t, err)

	// Test 1: Mettre à jour la permission à write
	reqBody := UpdateCollaboratorPermissionRequest{
		Permission: "write",
	}
	body, err := json.Marshal(reqBody)
	require.NoError(t, err)

	req := httptest.NewRequest("PUT", fmt.Sprintf("/api/v1/playlists/%d/collaborators/%d?user_id=%d", playlistID, collaboratorID, ownerID), 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)
	assert.Equal(t, "collaborator permission updated", response["message"])

	// Vérifier que la permission a été mise à jour
	var collaborator models.PlaylistCollaborator
	err = db.Where("playlist_id = ? AND user_id = ?", playlistID, collaboratorID).First(&collaborator).Error
	require.NoError(t, err)
	assert.Equal(t, models.PlaylistPermissionWrite, collaborator.Permission)

	// Test 2: Mettre à jour la permission à admin
	reqBody.Permission = "admin"
	body, err = json.Marshal(reqBody)
	require.NoError(t, err)

	req = httptest.NewRequest("PUT", fmt.Sprintf("/api/v1/playlists/%d/collaborators/%d?user_id=%d", playlistID, collaboratorID, ownerID), bytes.NewBuffer(body))
	req.Header.Set("Content-Type", "application/json")
	w = httptest.NewRecorder()
	router.ServeHTTP(w, req)

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

	// Vérifier que la permission a été mise à jour
	err = db.Where("playlist_id = ? AND user_id = ?", playlistID, collaboratorID).First(&collaborator).Error
	require.NoError(t, err)
	assert.Equal(t, models.PlaylistPermissionAdmin, collaborator.Permission)

	// Test 3: Essayer de mettre à jour sans être propriétaire (devrait échouer)
	otherUserID := int64(3)
	createTestUserForCollaboration(t, db, otherUserID, "other_user")
	req = httptest.NewRequest("PUT", fmt.Sprintf("/api/v1/playlists/%d/collaborators/%d?user_id=%d", playlistID, collaboratorID, otherUserID), bytes.NewBuffer(body))
	req.Header.Set("Content-Type", "application/json")
	w = httptest.NewRecorder()
	router.ServeHTTP(w, req)

	assert.Equal(t, http.StatusForbidden, w.Code)
}

// TestPlaylistCollaborationIntegration_GetCollaborators teste la récupération des collaborateurs
func TestPlaylistCollaborationIntegration_GetCollaborators(t *testing.T) {
	if testing.Short() {
		t.Skip("Skipping integration test in short mode")
	}

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

	// Créer des utilisateurs de test
	ownerID := int64(1)
	collaborator1ID := int64(2)
	collaborator2ID := int64(3)
	createTestUserForCollaboration(t, db, ownerID, "owner")
	createTestUserForCollaboration(t, db, collaborator1ID, "collaborator1")
	createTestUserForCollaboration(t, db, collaborator2ID, "collaborator2")

	// Créer une playlist
	playlistID := int64(1)
	createTestPlaylistForCollaboration(t, db, ownerID, playlistID)

	// Ajouter des collaborateurs
	playlistService := services.NewPlaylistServiceWithDB(db, zap.NewNop())
	_, err := playlistService.AddCollaborator(nil, playlistID, ownerID, collaborator1ID, models.PlaylistPermissionRead)
	require.NoError(t, err)
	_, err = playlistService.AddCollaborator(nil, playlistID, ownerID, collaborator2ID, models.PlaylistPermissionWrite)
	require.NoError(t, err)

	// Test 1: Récupérer les collaborateurs en tant que propriétaire
	req := httptest.NewRequest("GET", fmt.Sprintf("/api/v1/playlists/%d/collaborators?user_id=%d", playlistID, ownerID), 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)
	assert.NotNil(t, response["collaborators"])

	collaborators := response["collaborators"].([]interface{})
	assert.Len(t, collaborators, 2)

	// Test 2: Récupérer les collaborateurs en tant que collaborateur
	req = httptest.NewRequest("GET", fmt.Sprintf("/api/v1/playlists/%d/collaborators?user_id=%d", playlistID, collaborator1ID), nil)
	w = httptest.NewRecorder()
	router.ServeHTTP(w, req)

	assert.Equal(t, http.StatusOK, w.Code)
	err = json.Unmarshal(w.Body.Bytes(), &response)
	require.NoError(t, err)
	assert.NotNil(t, response["collaborators"])

	// Test 3: Essayer de récupérer les collaborateurs d'une playlist privée sans accès (devrait échouer)
	privatePlaylistID := int64(2)
	privatePlaylist := createTestPlaylistForCollaboration(t, db, ownerID, privatePlaylistID)
	privatePlaylist.IsPublic = false
	db.Save(privatePlaylist)

	otherUserID := int64(4)
	createTestUserForCollaboration(t, db, otherUserID, "other_user")
	req = httptest.NewRequest("GET", fmt.Sprintf("/api/v1/playlists/%d/collaborators?user_id=%d", privatePlaylistID, otherUserID), nil)
	w = httptest.NewRecorder()
	router.ServeHTTP(w, req)

	assert.Equal(t, http.StatusForbidden, w.Code)
}

// TestPlaylistCollaborationIntegration_CheckPermission teste la vérification des permissions
func TestPlaylistCollaborationIntegration_CheckPermission(t *testing.T) {
	if testing.Short() {
		t.Skip("Skipping integration test in short mode")
	}

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

	// Créer des utilisateurs de test
	ownerID := int64(1)
	collaboratorReadID := int64(2)
	collaboratorWriteID := int64(3)
	collaboratorAdminID := int64(4)
	createTestUserForCollaboration(t, db, ownerID, "owner")
	createTestUserForCollaboration(t, db, collaboratorReadID, "collaborator_read")
	createTestUserForCollaboration(t, db, collaboratorWriteID, "collaborator_write")
	createTestUserForCollaboration(t, db, collaboratorAdminID, "collaborator_admin")

	// Créer une playlist
	playlistID := int64(1)
	createTestPlaylistForCollaboration(t, db, ownerID, playlistID)

	// Ajouter des collaborateurs avec différentes permissions
	playlistService := services.NewPlaylistServiceWithDB(db, zap.NewNop())
	_, err := playlistService.AddCollaborator(nil, playlistID, ownerID, collaboratorReadID, models.PlaylistPermissionRead)
	require.NoError(t, err)
	_, err = playlistService.AddCollaborator(nil, playlistID, ownerID, collaboratorWriteID, models.PlaylistPermissionWrite)
	require.NoError(t, err)
	_, err = playlistService.AddCollaborator(nil, playlistID, ownerID, collaboratorAdminID, models.PlaylistPermissionAdmin)
	require.NoError(t, err)

	// Test 1: Vérifier que le propriétaire peut récupérer les collaborateurs (a toutes les permissions)
	req := httptest.NewRequest("GET", fmt.Sprintf("/api/v1/playlists/%d/collaborators?user_id=%d", playlistID, ownerID), nil)
	w := httptest.NewRecorder()
	router.ServeHTTP(w, req)
	assert.Equal(t, http.StatusOK, w.Code)

	// Test 2: Vérifier que le collaborateur read peut récupérer les collaborateurs
	req = httptest.NewRequest("GET", fmt.Sprintf("/api/v1/playlists/%d/collaborators?user_id=%d", playlistID, collaboratorReadID), nil)
	w = httptest.NewRecorder()
	router.ServeHTTP(w, req)
	assert.Equal(t, http.StatusOK, w.Code)

	// Test 3: Vérifier que le collaborateur write peut récupérer les collaborateurs
	req = httptest.NewRequest("GET", fmt.Sprintf("/api/v1/playlists/%d/collaborators?user_id=%d", playlistID, collaboratorWriteID), nil)
	w = httptest.NewRecorder()
	router.ServeHTTP(w, req)
	assert.Equal(t, http.StatusOK, w.Code)

	// Test 4: Vérifier que le collaborateur admin peut récupérer les collaborateurs
	req = httptest.NewRequest("GET", fmt.Sprintf("/api/v1/playlists/%d/collaborators?user_id=%d", playlistID, collaboratorAdminID), nil)
	w = httptest.NewRecorder()
	router.ServeHTTP(w, req)
	assert.Equal(t, http.StatusOK, w.Code)

	// Test 5: Vérifier qu'un utilisateur non collaborateur peut récupérer les collaborateurs d'une playlist publique
	otherUserID := int64(5)
	createTestUserForCollaboration(t, db, otherUserID, "other_user")
	req = httptest.NewRequest("GET", fmt.Sprintf("/api/v1/playlists/%d/collaborators?user_id=%d", playlistID, otherUserID), nil)
	w = httptest.NewRecorder()
	router.ServeHTTP(w, req)
	assert.Equal(t, http.StatusOK, w.Code)
}

// TestPlaylistCollaborationIntegration_CompleteFlow teste le flux complet de collaboration
func TestPlaylistCollaborationIntegration_CompleteFlow(t *testing.T) {
	if testing.Short() {
		t.Skip("Skipping integration test in short mode")
	}

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

	// Créer des utilisateurs de test
	ownerID := int64(1)
	collaboratorID := int64(2)
	createTestUserForCollaboration(t, db, ownerID, "owner")
	createTestUserForCollaboration(t, db, collaboratorID, "collaborator")

	// Créer une playlist
	playlistID := int64(1)
	createTestPlaylistForCollaboration(t, db, ownerID, playlistID)

	// Étape 1: Ajouter un collaborateur avec permission read
	reqBody := AddCollaboratorRequest{
		UserID:     collaboratorID,
		Permission: "read",
	}
	body, err := json.Marshal(reqBody)
	require.NoError(t, err)

	req := httptest.NewRequest("POST", fmt.Sprintf("/api/v1/playlists/%d/collaborators?user_id=%d", playlistID, ownerID), bytes.NewBuffer(body))
	req.Header.Set("Content-Type", "application/json")
	w := httptest.NewRecorder()
	router.ServeHTTP(w, req)
	assert.Equal(t, http.StatusCreated, w.Code)

	// Étape 2: Vérifier que le collaborateur peut récupérer les collaborateurs
	req = httptest.NewRequest("GET", fmt.Sprintf("/api/v1/playlists/%d/collaborators?user_id=%d", playlistID, collaboratorID), nil)
	w = httptest.NewRecorder()
	router.ServeHTTP(w, req)
	assert.Equal(t, http.StatusOK, w.Code)

	// Étape 3: Mettre à jour la permission à write
	updateReqBody := UpdateCollaboratorPermissionRequest{
		Permission: "write",
	}
	updateBody, err := json.Marshal(updateReqBody)
	require.NoError(t, err)

	req = httptest.NewRequest("PUT", fmt.Sprintf("/api/v1/playlists/%d/collaborators/%d?user_id=%d", playlistID, collaboratorID, ownerID), bytes.NewBuffer(updateBody))
	req.Header.Set("Content-Type", "application/json")
	w = httptest.NewRecorder()
	router.ServeHTTP(w, req)
	assert.Equal(t, http.StatusOK, w.Code)

	// Étape 4: Vérifier que la permission a été mise à jour
	var collaborator models.PlaylistCollaborator
	err = db.Where("playlist_id = ? AND user_id = ?", playlistID, collaboratorID).First(&collaborator).Error
	require.NoError(t, err)
	assert.Equal(t, models.PlaylistPermissionWrite, collaborator.Permission)

	// Étape 5: Retirer le collaborateur
	req = httptest.NewRequest("DELETE", fmt.Sprintf("/api/v1/playlists/%d/collaborators/%d?user_id=%d", playlistID, collaboratorID, ownerID), nil)
	w = httptest.NewRecorder()
	router.ServeHTTP(w, req)
	assert.Equal(t, http.StatusOK, w.Code)

	// Étape 6: Vérifier que le collaborateur a été supprimé
	var count int64
	db.Model(&models.PlaylistCollaborator{}).Where("playlist_id = ? AND user_id = ?", playlistID, collaboratorID).Count(&count)
	assert.Equal(t, int64(0), count)
}

