package services

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

// setupTestPermissionServiceDB crée une base de données de test pour PermissionService
func setupTestPermissionServiceDB(t *testing.T) *gorm.DB {
	db, err := gorm.Open(sqlite.Open(":memory:"), &gorm.Config{})
	require.NoError(t, err)

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

	// Create tables manually to ensure all columns exist (AutoMigrate might miss some in SQLite)
	db.Exec(`
		CREATE TABLE IF NOT EXISTS roles (
			id TEXT PRIMARY KEY,
			name TEXT NOT NULL UNIQUE,
			display_name TEXT NOT NULL,
			description TEXT,
			is_system INTEGER DEFAULT 0,
			is_active INTEGER DEFAULT 1,
			created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
			updated_at DATETIME DEFAULT CURRENT_TIMESTAMP
		)
	`)

	db.Exec(`
		CREATE TABLE IF NOT EXISTS permissions (
			id TEXT PRIMARY KEY,
			name TEXT NOT NULL UNIQUE,
			resource TEXT,
			action TEXT,
			description TEXT,
			created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
			updated_at DATETIME DEFAULT CURRENT_TIMESTAMP
		)
	`)

	db.Exec(`
		CREATE TABLE IF NOT EXISTS user_roles (
			id TEXT PRIMARY KEY,
			user_id TEXT NOT NULL,
			role_id TEXT NOT NULL,
			role TEXT NOT NULL,
			assigned_at DATETIME DEFAULT CURRENT_TIMESTAMP,
			assigned_by TEXT,
			expires_at DATETIME,
			is_active INTEGER DEFAULT 1,
			created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
			UNIQUE(user_id, role)
		)
	`)

	db.Exec(`
		CREATE TABLE IF NOT EXISTS role_permissions (
			id TEXT PRIMARY KEY,
			role_id TEXT NOT NULL,
			permission_id TEXT NOT NULL,
			created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
			UNIQUE(role_id, permission_id)
		)
	`)

	return db
}

// TestPermissionService_HasRole teste HasRole
// GO-007, GO-025: Test critique pour RBAC
func TestPermissionService_HasRole(t *testing.T) {
	db := setupTestPermissionServiceDB(t)
	service := NewPermissionService(db)
	ctx := context.Background()

	// Créer un rôle admin
	adminRole := &models.Role{
		ID:       uuid.New(),
		Name:     "admin",
		IsActive: true,
	}
	require.NoError(t, db.Create(adminRole).Error)

	// Créer un rôle user
	userRole := &models.Role{
		ID:       uuid.New(),
		Name:     "user",
		IsActive: true,
	}
	require.NoError(t, db.Create(userRole).Error)

	// Créer un utilisateur
	userID := uuid.New()

	// Test 1: Utilisateur sans rôle
	hasRole, err := service.HasRole(ctx, userID, "admin")
	require.NoError(t, err)
	assert.False(t, hasRole, "User without role should not have admin role")

	// Test 2: Utilisateur avec rôle admin
	userRoleRecord := &models.UserRole{
		ID:       uuid.New(),
		UserID:   userID,
		RoleID:   adminRole.ID,
		IsActive: true,
	}
	require.NoError(t, db.Create(userRoleRecord).Error)

	hasRole, err = service.HasRole(ctx, userID, "admin")
	require.NoError(t, err)
	assert.True(t, hasRole, "User with admin role should have admin role")

	// Test 3: Utilisateur avec rôle user (pas admin)
	hasRole, err = service.HasRole(ctx, userID, "user")
	require.NoError(t, err)
	assert.False(t, hasRole, "User with admin role should not have user role")

	// Test 4: Rôle inactif
	userRoleRecord.IsActive = false
	require.NoError(t, db.Save(userRoleRecord).Error)

	hasRole, err = service.HasRole(ctx, userID, "admin")
	require.NoError(t, err)
	assert.False(t, hasRole, "User with inactive role should not have role")

	// Test 5: Rôle expiré
	userRoleRecord.IsActive = true
	expiredTime := time.Now().Add(-1 * time.Hour)
	userRoleRecord.ExpiresAt = &expiredTime
	require.NoError(t, db.Save(userRoleRecord).Error)

	hasRole, err = service.HasRole(ctx, userID, "admin")
	require.NoError(t, err)
	assert.False(t, hasRole, "User with expired role should not have role")
}

// TestPermissionService_HasPermission teste HasPermission
// GO-007, GO-025: Test critique pour RBAC
func TestPermissionService_HasPermission(t *testing.T) {
	db := setupTestPermissionServiceDB(t)
	service := NewPermissionService(db)
	ctx := context.Background()

	// Créer un rôle
	role := &models.Role{
		ID:       uuid.New(),
		Name:     "admin",
		IsActive: true,
	}
	require.NoError(t, db.Create(role).Error)

	// Créer une permission
	permission := &models.Permission{
		ID:       uuid.New(),
		Name:     "manage_users",
		Resource: "users",
		Action:   "manage",
	}
	require.NoError(t, db.Create(permission).Error)

	// Créer un utilisateur
	userID := uuid.New()

	// Test 1: Utilisateur sans permission
	hasPermission, err := service.HasPermission(ctx, userID, "manage_users")
	require.NoError(t, err)
	assert.False(t, hasPermission, "User without permission should not have permission")

	// Test 2: Assigner rôle à l'utilisateur
	userRole := &models.UserRole{
		ID:       uuid.New(),
		UserID:   userID,
		RoleID:   role.ID,
		IsActive: true,
	}
	require.NoError(t, db.Create(userRole).Error)

	// Test 3: Permission non assignée au rôle
	hasPermission, err = service.HasPermission(ctx, userID, "manage_users")
	require.NoError(t, err)
	assert.False(t, hasPermission, "User with role but without permission should not have permission")

	// Test 4: Assigner permission au rôle
	rolePermission := &models.RolePermission{
		RoleID:       role.ID,
		PermissionID: permission.ID,
	}
	require.NoError(t, db.Create(rolePermission).Error)

	hasPermission, err = service.HasPermission(ctx, userID, "manage_users")
	require.NoError(t, err)
	assert.True(t, hasPermission, "User with role and permission should have permission")

	// Test 5: Permission inexistante
	hasPermission, err = service.HasPermission(ctx, userID, "nonexistent_permission")
	require.NoError(t, err)
	assert.False(t, hasPermission, "User should not have nonexistent permission")
}

// TestPermissionService_GetRolePermissions teste GetRolePermissions
func TestPermissionService_GetRolePermissions(t *testing.T) {
	db := setupTestPermissionServiceDB(t)
	service := NewPermissionService(db)
	ctx := context.Background()

	// Créer un rôle
	role := &models.Role{
		ID:       uuid.New(),
		Name:     "admin",
		IsActive: true,
	}
	require.NoError(t, db.Create(role).Error)

	// Créer des permissions
	perm1 := &models.Permission{
		ID:       uuid.New(),
		Name:     "manage_users",
		Resource: "users",
		Action:   "manage",
	}
	require.NoError(t, db.Create(perm1).Error)

	perm2 := &models.Permission{
		ID:       uuid.New(),
		Name:     "manage_tracks",
		Resource: "tracks",
		Action:   "manage",
	}
	require.NoError(t, db.Create(perm2).Error)

	// Test 1: Rôle sans permissions
	permissions, err := service.GetRolePermissions(ctx, role.ID)
	require.NoError(t, err)
	assert.Empty(t, permissions, "Role without permissions should return empty list")

	// Test 2: Assigner permissions au rôle
	rolePerm1 := &models.RolePermission{
		RoleID:       role.ID,
		PermissionID: perm1.ID,
	}
	require.NoError(t, db.Create(rolePerm1).Error)

	rolePerm2 := &models.RolePermission{
		RoleID:       role.ID,
		PermissionID: perm2.ID,
	}
	require.NoError(t, db.Create(rolePerm2).Error)

	permissions, err = service.GetRolePermissions(ctx, role.ID)
	require.NoError(t, err)
	assert.Len(t, permissions, 2, "Role should have 2 permissions")
}

// TestPermissionService_AssignPermissionToRole teste AssignPermissionToRole
func TestPermissionService_AssignPermissionToRole(t *testing.T) {
	db := setupTestPermissionServiceDB(t)
	service := NewPermissionService(db)
	ctx := context.Background()

	// Créer un rôle et une permission
	role := &models.Role{
		ID:       uuid.New(),
		Name:     "admin",
		IsActive: true,
	}
	require.NoError(t, db.Create(role).Error)

	permission := &models.Permission{
		ID:       uuid.New(),
		Name:     "manage_users",
		Resource: "users",
		Action:   "manage",
	}
	require.NoError(t, db.Create(permission).Error)

	// Assigner permission au rôle
	err := service.AssignPermissionToRole(ctx, role.ID, permission.ID)
	require.NoError(t, err)

	// Vérifier que l'assignation existe
	var rolePermission models.RolePermission
	err = db.Where("role_id = ? AND permission_id = ?", role.ID, permission.ID).First(&rolePermission).Error
	require.NoError(t, err)
	assert.Equal(t, role.ID, rolePermission.RoleID)
	assert.Equal(t, permission.ID, rolePermission.PermissionID)
}

// TestPermissionService_RevokePermissionFromRole teste RevokePermissionFromRole
func TestPermissionService_RevokePermissionFromRole(t *testing.T) {
	db := setupTestPermissionServiceDB(t)
	service := NewPermissionService(db)
	ctx := context.Background()

	// Créer un rôle et une permission
	role := &models.Role{
		ID:       uuid.New(),
		Name:     "admin",
		IsActive: true,
	}
	require.NoError(t, db.Create(role).Error)

	permission := &models.Permission{
		ID:       uuid.New(),
		Name:     "manage_users",
		Resource: "users",
		Action:   "manage",
	}
	require.NoError(t, db.Create(permission).Error)

	// Assigner permission au rôle
	rolePermission := &models.RolePermission{
		RoleID:       role.ID,
		PermissionID: permission.ID,
	}
	require.NoError(t, db.Create(rolePermission).Error)

	// Révoquer permission
	err := service.RevokePermissionFromRole(ctx, role.ID, permission.ID)
	require.NoError(t, err)

	// Vérifier que l'assignation n'existe plus
	var count int64
	db.Model(&models.RolePermission{}).
		Where("role_id = ? AND permission_id = ?", role.ID, permission.ID).
		Count(&count)
	assert.Equal(t, int64(0), count, "Permission should be revoked")

	// Test: Révoquer une permission inexistante
	err = service.RevokePermissionFromRole(ctx, role.ID, permission.ID)
	assert.Error(t, err, "Revoking nonexistent permission should return error")
	assert.Contains(t, err.Error(), "not found")
}
