package services

import (
	"context"
	"testing"

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

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

func setupTestRoleService(t *testing.T) (*RoleService, *gorm.DB, func()) {
	db, err := gorm.Open(sqlite.Open(":memory:"), &gorm.Config{})
	require.NoError(t, err)

	db.Exec("PRAGMA foreign_keys = ON")

	err = db.AutoMigrate(
		&models.User{},
		&models.Role{},
		&models.Permission{},
		&models.UserRole{},
		&models.RolePermission{},
	)
	require.NoError(t, err)

	service := NewRoleService(db)

	cleanup := func() {
		sqlDB, _ := db.DB()
		if sqlDB != nil {
			sqlDB.Close()
		}
	}

	return service, db, cleanup
}

func TestRoleService_NewRoleService(t *testing.T) {
	_, db, cleanup := setupTestRoleService(t)
	defer cleanup()

	service := NewRoleService(db)

	assert.NotNil(t, service)
	assert.NotNil(t, service.db)
}

func TestRoleService_GetRoles_Empty(t *testing.T) {
	service, _, cleanup := setupTestRoleService(t)
	defer cleanup()

	ctx := context.Background()
	roles, err := service.GetRoles(ctx)

	assert.NoError(t, err)
	assert.NotNil(t, roles)
	assert.Len(t, roles, 0)
}

func TestRoleService_CreateRole_Success(t *testing.T) {
	service, db, cleanup := setupTestRoleService(t)
	defer cleanup()

	ctx := context.Background()
	role := &models.Role{
		Name:        "test_role",
		Description: "Test role description",
		IsSystem:    false,
	}

	err := service.CreateRole(ctx, role)
	assert.NoError(t, err)
	assert.NotEqual(t, uuid.Nil, role.ID)

	// Verify role was created
	var createdRole models.Role
	err = db.First(&createdRole, role.ID).Error
	assert.NoError(t, err)
	assert.Equal(t, "test_role", createdRole.Name)
	assert.Equal(t, "Test role description", createdRole.Description)
	assert.False(t, createdRole.IsSystem)
}

func TestRoleService_GetRole_Success(t *testing.T) {
	service, db, cleanup := setupTestRoleService(t)
	defer cleanup()

	ctx := context.Background()
	role := &models.Role{
		Name:        "test_role",
		Description: "Test role description",
		IsSystem:    false,
	}
	err := db.Create(role).Error
	require.NoError(t, err)

	retrievedRole, err := service.GetRole(ctx, role.ID)
	assert.NoError(t, err)
	assert.NotNil(t, retrievedRole)
	assert.Equal(t, role.ID, retrievedRole.ID)
	assert.Equal(t, "test_role", retrievedRole.Name)
}

func TestRoleService_GetRole_NotFound(t *testing.T) {
	service, _, cleanup := setupTestRoleService(t)
	defer cleanup()

	ctx := context.Background()
	nonExistentID := uuid.New()

	role, err := service.GetRole(ctx, nonExistentID)
	assert.Error(t, err)
	assert.Nil(t, role)
	assert.Contains(t, err.Error(), "role not found")
}

func TestRoleService_UpdateRole_Success(t *testing.T) {
	service, db, cleanup := setupTestRoleService(t)
	defer cleanup()

	ctx := context.Background()
	role := &models.Role{
		Name:        "test_role",
		Description: "Original description",
		IsSystem:    false,
	}
	err := db.Create(role).Error
	require.NoError(t, err)

	updates := &models.Role{
		Name:        "updated_role",
		Description: "Updated description",
	}

	err = service.UpdateRole(ctx, role.ID, updates)
	assert.NoError(t, err)

	// Verify role was updated
	var updatedRole models.Role
	err = db.First(&updatedRole, role.ID).Error
	assert.NoError(t, err)
	assert.Equal(t, "updated_role", updatedRole.Name)
	assert.Equal(t, "Updated description", updatedRole.Description)
}

func TestRoleService_UpdateRole_SystemRole(t *testing.T) {
	service, db, cleanup := setupTestRoleService(t)
	defer cleanup()

	ctx := context.Background()
	role := &models.Role{
		Name:        "system_role",
		Description: "System role",
		IsSystem:    true,
	}
	err := db.Create(role).Error
	require.NoError(t, err)

	updates := &models.Role{
		Name: "updated_system_role",
	}

	err = service.UpdateRole(ctx, role.ID, updates)
	assert.Error(t, err)
	assert.Contains(t, err.Error(), "role not found or is system role")
}

func TestRoleService_UpdateRole_NotFound(t *testing.T) {
	service, _, cleanup := setupTestRoleService(t)
	defer cleanup()

	ctx := context.Background()
	nonExistentID := uuid.New()

	updates := &models.Role{
		Name: "updated_role",
	}

	err := service.UpdateRole(ctx, nonExistentID, updates)
	assert.Error(t, err)
	assert.Contains(t, err.Error(), "role not found or is system role")
}

func TestRoleService_DeleteRole_Success(t *testing.T) {
	service, db, cleanup := setupTestRoleService(t)
	defer cleanup()

	ctx := context.Background()
	role := &models.Role{
		Name:        "test_role",
		Description: "Test role",
		IsSystem:    false,
	}
	err := db.Create(role).Error
	require.NoError(t, err)

	err = service.DeleteRole(ctx, role.ID)
	assert.NoError(t, err)

	// Verify role was deleted
	var deletedRole models.Role
	err = db.First(&deletedRole, role.ID).Error
	assert.Error(t, err)
	assert.Equal(t, gorm.ErrRecordNotFound, err)
}

func TestRoleService_DeleteRole_SystemRole(t *testing.T) {
	service, db, cleanup := setupTestRoleService(t)
	defer cleanup()

	ctx := context.Background()
	role := &models.Role{
		Name:        "system_role",
		Description: "System role",
		IsSystem:    true,
	}
	err := db.Create(role).Error
	require.NoError(t, err)

	err = service.DeleteRole(ctx, role.ID)
	assert.Error(t, err)
	assert.Contains(t, err.Error(), "cannot delete system role")
}

func TestRoleService_DeleteRole_NotFound(t *testing.T) {
	service, _, cleanup := setupTestRoleService(t)
	defer cleanup()

	ctx := context.Background()
	nonExistentID := uuid.New()

	err := service.DeleteRole(ctx, nonExistentID)
	assert.Error(t, err)
	assert.Contains(t, err.Error(), "role not found")
}

func TestRoleService_AssignRoleToUser_Success(t *testing.T) {
	service, db, cleanup := setupTestRoleService(t)
	defer cleanup()

	ctx := context.Background()
	user := &models.User{
		Username:     "testuser",
		Email:        "test@example.com",
		PasswordHash: "hash",
		IsActive:     true,
	}
	err := db.Create(user).Error
	require.NoError(t, err)

	role := &models.Role{
		Name:        "test_role",
		Description: "Test role",
		IsSystem:    false,
	}
	err = db.Create(role).Error
	require.NoError(t, err)

	assignedBy := uuid.New()
	err = service.AssignRoleToUser(ctx, user.ID, role.ID, assignedBy, nil)
	assert.NoError(t, err)

	// Verify role was assigned
	var userRole models.UserRole
	err = db.Where("user_id = ? AND role_id = ?", user.ID, role.ID).First(&userRole).Error
	assert.NoError(t, err)
	assert.True(t, userRole.IsActive)
}

func TestRoleService_RevokeRoleFromUser_Success(t *testing.T) {
	service, db, cleanup := setupTestRoleService(t)
	defer cleanup()

	ctx := context.Background()
	user := &models.User{
		Username:     "testuser",
		Email:        "test@example.com",
		PasswordHash: "hash",
		IsActive:     true,
	}
	err := db.Create(user).Error
	require.NoError(t, err)

	role := &models.Role{
		Name:        "test_role",
		Description: "Test role",
		IsSystem:    false,
	}
	err = db.Create(role).Error
	require.NoError(t, err)

	assignedBy := uuid.New()
	err = service.AssignRoleToUser(ctx, user.ID, role.ID, assignedBy, nil)
	require.NoError(t, err)

	err = service.RevokeRoleFromUser(ctx, user.ID, role.ID)
	assert.NoError(t, err)

	// Verify role was revoked
	var userRole models.UserRole
	err = db.Where("user_id = ? AND role_id = ?", user.ID, role.ID).First(&userRole).Error
	assert.NoError(t, err)
	assert.False(t, userRole.IsActive)
}

func TestRoleService_RevokeRoleFromUser_NotFound(t *testing.T) {
	service, db, cleanup := setupTestRoleService(t)
	defer cleanup()

	ctx := context.Background()
	user := &models.User{
		Username:     "testuser",
		Email:        "test@example.com",
		PasswordHash: "hash",
		IsActive:     true,
	}
	err := db.Create(user).Error
	require.NoError(t, err)

	role := &models.Role{
		Name:        "test_role",
		Description: "Test role",
		IsSystem:    false,
	}
	err = db.Create(role).Error
	require.NoError(t, err)

	err = service.RevokeRoleFromUser(ctx, user.ID, role.ID)
	assert.Error(t, err)
	assert.Contains(t, err.Error(), "role assignment not found")
}

func TestRoleService_GetUserRoles_Success(t *testing.T) {
	service, db, cleanup := setupTestRoleService(t)
	defer cleanup()

	ctx := context.Background()
	user := &models.User{
		Username:     "testuser",
		Email:        "test@example.com",
		PasswordHash: "hash",
		IsActive:     true,
	}
	err := db.Create(user).Error
	require.NoError(t, err)

	role1 := &models.Role{
		Name:        "role1",
		Description: "Role 1",
		IsSystem:    false,
	}
	err = db.Create(role1).Error
	require.NoError(t, err)

	role2 := &models.Role{
		Name:        "role2",
		Description: "Role 2",
		IsSystem:    false,
	}
	err = db.Create(role2).Error
	require.NoError(t, err)

	assignedBy := uuid.New()
	err = service.AssignRoleToUser(ctx, user.ID, role1.ID, assignedBy, nil)
	require.NoError(t, err)

	err = service.AssignRoleToUser(ctx, user.ID, role2.ID, assignedBy, nil)
	require.NoError(t, err)

	roles, err := service.GetUserRoles(ctx, user.ID)
	assert.NoError(t, err)
	assert.Len(t, roles, 2) // Both roles should be active
}

func TestRoleService_GetUserRoles_Empty(t *testing.T) {
	service, db, cleanup := setupTestRoleService(t)
	defer cleanup()

	ctx := context.Background()
	user := &models.User{
		Username:     "testuser",
		Email:        "test@example.com",
		PasswordHash: "hash",
		IsActive:     true,
	}
	err := db.Create(user).Error
	require.NoError(t, err)

	roles, err := service.GetUserRoles(ctx, user.ID)
	assert.NoError(t, err)
	assert.Len(t, roles, 0)
}

func TestRoleService_HasRole_True(t *testing.T) {
	service, db, cleanup := setupTestRoleService(t)
	defer cleanup()

	ctx := context.Background()
	user := &models.User{
		Username:     "testuser",
		Email:        "test@example.com",
		PasswordHash: "hash",
		IsActive:     true,
	}
	err := db.Create(user).Error
	require.NoError(t, err)

	role := &models.Role{
		Name:        "test_role",
		Description: "Test role",
		IsSystem:    false,
	}
	err = db.Create(role).Error
	require.NoError(t, err)

	assignedBy := uuid.New()
	err = service.AssignRoleToUser(ctx, user.ID, role.ID, assignedBy, nil)
	require.NoError(t, err)

	hasRole, err := service.HasRole(ctx, user.ID, "test_role")
	assert.NoError(t, err)
	assert.True(t, hasRole)
}

func TestRoleService_HasRole_False(t *testing.T) {
	service, db, cleanup := setupTestRoleService(t)
	defer cleanup()

	ctx := context.Background()
	user := &models.User{
		Username:     "testuser",
		Email:        "test@example.com",
		PasswordHash: "hash",
		IsActive:     true,
	}
	err := db.Create(user).Error
	require.NoError(t, err)

	hasRole, err := service.HasRole(ctx, user.ID, "non_existent_role")
	assert.NoError(t, err)
	assert.False(t, hasRole)
}

func TestRoleService_HasPermission_True(t *testing.T) {
	service, db, cleanup := setupTestRoleService(t)
	defer cleanup()

	ctx := context.Background()
	user := &models.User{
		Username:     "testuser",
		Email:        "test@example.com",
		PasswordHash: "hash",
		IsActive:     true,
	}
	err := db.Create(user).Error
	require.NoError(t, err)

	role := &models.Role{
		Name:        "test_role",
		Description: "Test role",
		IsSystem:    false,
	}
	err = db.Create(role).Error
	require.NoError(t, err)

	permission := &models.Permission{
		Name:        "test_permission",
		Description: "Test permission",
		Resource:    "tracks",
		Action:      "read",
	}
	err = db.Create(permission).Error
	require.NoError(t, err)

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

	assignedBy := uuid.New()
	err = service.AssignRoleToUser(ctx, user.ID, role.ID, assignedBy, nil)
	require.NoError(t, err)

	hasPermission, err := service.HasPermission(ctx, user.ID, "tracks", "read")
	assert.NoError(t, err)
	assert.True(t, hasPermission)
}

func TestRoleService_HasPermission_False(t *testing.T) {
	service, db, cleanup := setupTestRoleService(t)
	defer cleanup()

	ctx := context.Background()
	user := &models.User{
		Username:     "testuser",
		Email:        "test@example.com",
		PasswordHash: "hash",
		IsActive:     true,
	}
	err := db.Create(user).Error
	require.NoError(t, err)

	hasPermission, err := service.HasPermission(ctx, user.ID, "tracks", "write")
	assert.NoError(t, err)
	assert.False(t, hasPermission)
}

func TestRoleService_GetRoles_WithPermissions(t *testing.T) {
	service, db, cleanup := setupTestRoleService(t)
	defer cleanup()

	ctx := context.Background()
	role := &models.Role{
		Name:        "test_role",
		Description: "Test role",
		IsSystem:    false,
	}
	err := db.Create(role).Error
	require.NoError(t, err)

	permission := &models.Permission{
		Name:        "test_permission",
		Description: "Test permission",
		Resource:    "tracks",
		Action:      "read",
	}
	err = db.Create(permission).Error
	require.NoError(t, err)

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

	roles, err := service.GetRoles(ctx)
	assert.NoError(t, err)
	assert.Len(t, roles, 1)
	assert.Len(t, roles[0].Permissions, 1)
	assert.Equal(t, "test_permission", roles[0].Permissions[0].Name)
}

func TestRoleService_GetRole_WithPermissions(t *testing.T) {
	service, db, cleanup := setupTestRoleService(t)
	defer cleanup()

	ctx := context.Background()
	role := &models.Role{
		Name:        "test_role",
		Description: "Test role",
		IsSystem:    false,
	}
	err := db.Create(role).Error
	require.NoError(t, err)

	permission := &models.Permission{
		Name:        "test_permission",
		Description: "Test permission",
		Resource:    "tracks",
		Action:      "read",
	}
	err = db.Create(permission).Error
	require.NoError(t, err)

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

	retrievedRole, err := service.GetRole(ctx, role.ID)
	assert.NoError(t, err)
	assert.NotNil(t, retrievedRole)
	assert.Len(t, retrievedRole.Permissions, 1)
	assert.Equal(t, "test_permission", retrievedRole.Permissions[0].Name)
}
