package repositories

import (
	"context"
	"testing"
	"time"

	"veza-backend-api/internal/models"

	"github.com/google/uuid"
	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"
	"gorm.io/driver/sqlite"
	"gorm.io/gorm"
)

// setupTestCollaboratorDB crée une base de données de test en mémoire
func setupTestCollaboratorDB(t *testing.T) *gorm.DB {
	db, err := gorm.Open(sqlite.Open(":memory:"), &gorm.Config{})
	require.NoError(t, err, "Failed to open test database")

	// Auto-migrate tous les modèles nécessaires
	err = db.AutoMigrate(
		&models.User{},
		&models.Playlist{},
		&models.PlaylistCollaborator{},
	)
	require.NoError(t, err, "Failed to migrate test database")

	return db
}

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

// createTestPlaylist crée une playlist de test
func createTestPlaylistForCollaborator(t *testing.T, db *gorm.DB, userID uuid.UUID) *models.Playlist {
	playlist := &models.Playlist{
		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
}

func TestNewPlaylistCollaboratorRepository(t *testing.T) {
	db := setupTestCollaboratorDB(t)
	repo := NewPlaylistCollaboratorRepository(db)
	assert.NotNil(t, repo)
}

func TestPlaylistCollaboratorRepository_AddCollaborator(t *testing.T) {
	db := setupTestCollaboratorDB(t)
	repo := NewPlaylistCollaboratorRepository(db)
	ctx := context.Background()

	owner := createTestUserForCollaborator(t, db, "owner")
	collaborator := createTestUserForCollaborator(t, db, "collaborator")
	playlist := createTestPlaylistForCollaborator(t, db, owner.ID)

	// Test AddCollaborator avec permission read
	collab, err := repo.AddCollaborator(ctx, playlist.ID, collaborator.ID, models.PlaylistPermissionRead)
	assert.NoError(t, err)
	assert.NotNil(t, collab)
	assert.Equal(t, playlist.ID, collab.PlaylistID)
	assert.Equal(t, collaborator.ID, collab.UserID)
	assert.Equal(t, models.PlaylistPermissionRead, collab.Permission)

	// Test AddCollaborator avec permission write
	collab2, err := repo.AddCollaborator(ctx, playlist.ID, owner.ID, models.PlaylistPermissionWrite)
	assert.NoError(t, err)
	assert.Equal(t, models.PlaylistPermissionWrite, collab2.Permission)

	// Test AddCollaborator avec permission invalide
	_, err = repo.AddCollaborator(ctx, playlist.ID, collaborator.ID, models.PlaylistPermission("invalid"))
	assert.Error(t, err)
	assert.Contains(t, err.Error(), "invalid permission")

	// Test AddCollaborator avec collaborateur déjà existant
	_, err = repo.AddCollaborator(ctx, playlist.ID, collaborator.ID, models.PlaylistPermissionWrite)
	assert.Error(t, err)
	assert.Contains(t, err.Error(), "already exists")
}

func TestPlaylistCollaboratorRepository_RemoveCollaborator(t *testing.T) {
	db := setupTestCollaboratorDB(t)
	repo := NewPlaylistCollaboratorRepository(db)
	ctx := context.Background()

	owner := createTestUserForCollaborator(t, db, "owner")
	collaborator := createTestUserForCollaborator(t, db, "collaborator")
	playlist := createTestPlaylistForCollaborator(t, db, owner.ID)

	// Ajouter un collaborateur
	_, err := repo.AddCollaborator(ctx, playlist.ID, collaborator.ID, models.PlaylistPermissionRead)
	assert.NoError(t, err)

	// Vérifier qu'il existe
	exists, err := repo.Exists(ctx, playlist.ID, collaborator.ID)
	assert.NoError(t, err)
	assert.True(t, exists)

	// Retirer le collaborateur
	err = repo.RemoveCollaborator(ctx, playlist.ID, collaborator.ID)
	assert.NoError(t, err)

	// Vérifier qu'il n'existe plus
	exists, err = repo.Exists(ctx, playlist.ID, collaborator.ID)
	assert.NoError(t, err)
	assert.False(t, exists)

	// Test RemoveCollaborator avec collaborateur inexistant
	err = repo.RemoveCollaborator(ctx, playlist.ID, uuid.New())
	assert.Error(t, err)
	assert.Equal(t, gorm.ErrRecordNotFound, err)
}

func TestPlaylistCollaboratorRepository_GetCollaborators(t *testing.T) {
	db := setupTestCollaboratorDB(t)
	repo := NewPlaylistCollaboratorRepository(db)
	ctx := context.Background()

	owner := createTestUserForCollaborator(t, db, "owner")
	collaborator1 := createTestUserForCollaborator(t, db, "collaborator1")
	collaborator2 := createTestUserForCollaborator(t, db, "collaborator2")
	playlist := createTestPlaylistForCollaborator(t, db, owner.ID)

	// Ajouter des collaborateurs
	_, err := repo.AddCollaborator(ctx, playlist.ID, collaborator1.ID, models.PlaylistPermissionRead)
	assert.NoError(t, err)

	_, err = repo.AddCollaborator(ctx, playlist.ID, collaborator2.ID, models.PlaylistPermissionWrite)
	assert.NoError(t, err)

	// Récupérer tous les collaborateurs
	collaborators, err := repo.GetCollaborators(ctx, playlist.ID)
	assert.NoError(t, err)
	assert.Len(t, collaborators, 2)

	// Vérifier les permissions
	permissions := make(map[uuid.UUID]models.PlaylistPermission)
	for _, collab := range collaborators {
		permissions[collab.UserID] = collab.Permission
	}
	assert.Equal(t, models.PlaylistPermissionRead, permissions[collaborator1.ID])
	assert.Equal(t, models.PlaylistPermissionWrite, permissions[collaborator2.ID])

	// Test GetCollaborators avec playlist sans collaborateurs
	playlist2 := createTestPlaylistForCollaborator(t, db, owner.ID)
	collaborators, err = repo.GetCollaborators(ctx, playlist2.ID)
	assert.NoError(t, err)
	assert.Len(t, collaborators, 0)
}

func TestPlaylistCollaboratorRepository_GetCollaborator(t *testing.T) {
	db := setupTestCollaboratorDB(t)
	repo := NewPlaylistCollaboratorRepository(db)
	ctx := context.Background()

	owner := createTestUserForCollaborator(t, db, "owner")
	collaborator := createTestUserForCollaborator(t, db, "collaborator")
	playlist := createTestPlaylistForCollaborator(t, db, owner.ID)

	// Ajouter un collaborateur
	_, err := repo.AddCollaborator(ctx, playlist.ID, collaborator.ID, models.PlaylistPermissionWrite)
	assert.NoError(t, err)

	// Récupérer le collaborateur
	collab, err := repo.GetCollaborator(ctx, playlist.ID, collaborator.ID)
	assert.NoError(t, err)
	assert.NotNil(t, collab)
	assert.Equal(t, playlist.ID, collab.PlaylistID)
	assert.Equal(t, collaborator.ID, collab.UserID)
	assert.Equal(t, models.PlaylistPermissionWrite, collab.Permission)

	// Test GetCollaborator avec collaborateur inexistant
	_, err = repo.GetCollaborator(ctx, playlist.ID, uuid.New())
	assert.Error(t, err)
	assert.Equal(t, gorm.ErrRecordNotFound, err)
}

func TestPlaylistCollaboratorRepository_UpdatePermission(t *testing.T) {
	db := setupTestCollaboratorDB(t)
	repo := NewPlaylistCollaboratorRepository(db)
	ctx := context.Background()

	owner := createTestUserForCollaborator(t, db, "owner")
	collaborator := createTestUserForCollaborator(t, db, "collaborator")
	playlist := createTestPlaylistForCollaborator(t, db, owner.ID)

	// Ajouter un collaborateur avec permission read
	_, err := repo.AddCollaborator(ctx, playlist.ID, collaborator.ID, models.PlaylistPermissionRead)
	assert.NoError(t, err)

	// Mettre à jour la permission à write
	err = repo.UpdatePermission(ctx, playlist.ID, collaborator.ID, models.PlaylistPermissionWrite)
	assert.NoError(t, err)

	// Vérifier la mise à jour
	collab, err := repo.GetCollaborator(ctx, playlist.ID, collaborator.ID)
	assert.NoError(t, err)
	assert.Equal(t, models.PlaylistPermissionWrite, collab.Permission)

	// Mettre à jour la permission à admin
	err = repo.UpdatePermission(ctx, playlist.ID, collaborator.ID, models.PlaylistPermissionAdmin)
	assert.NoError(t, err)

	// Vérifier la mise à jour
	collab, err = repo.GetCollaborator(ctx, playlist.ID, collaborator.ID)
	assert.NoError(t, err)
	assert.Equal(t, models.PlaylistPermissionAdmin, collab.Permission)

	// Test UpdatePermission avec permission invalide
	err = repo.UpdatePermission(ctx, playlist.ID, collaborator.ID, models.PlaylistPermission("invalid"))
	assert.Error(t, err)
	assert.Contains(t, err.Error(), "invalid permission")

	// Test UpdatePermission avec collaborateur inexistant
	err = repo.UpdatePermission(ctx, playlist.ID, uuid.New(), models.PlaylistPermissionRead)
	assert.Error(t, err)
	assert.Equal(t, gorm.ErrRecordNotFound, err)
}

func TestPlaylistCollaboratorRepository_GetByUserID(t *testing.T) {
	db := setupTestCollaboratorDB(t)
	repo := NewPlaylistCollaboratorRepository(db)
	ctx := context.Background()

	owner := createTestUserForCollaborator(t, db, "owner")
	collaborator := createTestUserForCollaborator(t, db, "collaborator")
	playlist1 := createTestPlaylistForCollaborator(t, db, owner.ID)
	playlist2 := createTestPlaylistForCollaborator(t, db, owner.ID)

	// Ajouter le collaborateur à plusieurs playlists
	_, err := repo.AddCollaborator(ctx, playlist1.ID, collaborator.ID, models.PlaylistPermissionRead)
	assert.NoError(t, err)

	_, err = repo.AddCollaborator(ctx, playlist2.ID, collaborator.ID, models.PlaylistPermissionWrite)
	assert.NoError(t, err)

	// Récupérer toutes les playlists où l'utilisateur est collaborateur
	collaborators, err := repo.GetByUserID(ctx, collaborator.ID)
	assert.NoError(t, err)
	assert.Len(t, collaborators, 2)

	// Vérifier les playlists
	playlistIDs := make(map[uuid.UUID]models.PlaylistPermission)
	for _, collab := range collaborators {
		playlistIDs[collab.PlaylistID] = collab.Permission
	}
	assert.Equal(t, models.PlaylistPermissionRead, playlistIDs[playlist1.ID])
	assert.Equal(t, models.PlaylistPermissionWrite, playlistIDs[playlist2.ID])
}

func TestPlaylistCollaboratorRepository_Exists(t *testing.T) {
	db := setupTestCollaboratorDB(t)
	repo := NewPlaylistCollaboratorRepository(db)
	ctx := context.Background()

	owner := createTestUserForCollaborator(t, db, "owner")
	collaborator := createTestUserForCollaborator(t, db, "collaborator")
	playlist := createTestPlaylistForCollaborator(t, db, owner.ID)

	// Vérifier qu'il n'existe pas
	exists, err := repo.Exists(ctx, playlist.ID, collaborator.ID)
	assert.NoError(t, err)
	assert.False(t, exists)

	// Ajouter un collaborateur
	_, err = repo.AddCollaborator(ctx, playlist.ID, collaborator.ID, models.PlaylistPermissionRead)
	assert.NoError(t, err)

	// Vérifier qu'il existe maintenant
	exists, err = repo.Exists(ctx, playlist.ID, collaborator.ID)
	assert.NoError(t, err)
	assert.True(t, exists)
}

func TestPlaylistCollaboratorRepository_AllPermissions(t *testing.T) {
	db := setupTestCollaboratorDB(t)
	repo := NewPlaylistCollaboratorRepository(db)
	ctx := context.Background()

	owner := createTestUserForCollaborator(t, db, "owner")
	playlist := createTestPlaylistForCollaborator(t, db, owner.ID)

	// Tester toutes les permissions
	permissions := []models.PlaylistPermission{
		models.PlaylistPermissionRead,
		models.PlaylistPermissionWrite,
		models.PlaylistPermissionAdmin,
	}

	for i, perm := range permissions {
		user := createTestUserForCollaborator(t, db, "user"+string(rune('0'+i)))
		collab, err := repo.AddCollaborator(ctx, playlist.ID, user.ID, perm)
		assert.NoError(t, err)
		assert.Equal(t, perm, collab.Permission)

		// Vérifier les méthodes de permission
		assert.True(t, collab.CanRead())
		if perm == models.PlaylistPermissionWrite || perm == models.PlaylistPermissionAdmin {
			assert.True(t, collab.CanWrite())
		} else {
			assert.False(t, collab.CanWrite())
		}
		if perm == models.PlaylistPermissionAdmin {
			assert.True(t, collab.CanAdmin())
		} else {
			assert.False(t, collab.CanAdmin())
		}
	}
}
