using Microsoft.Extensions.Caching.Memory;
using OmsorgCore.Application.Services;
using OmsorgCore.Domain.Entities;
using OmsorgCore.Domain.Enums;
using OmsorgCore.Tests.TestDoubles;

namespace OmsorgCore.Tests.Services;

public class RoleServiceTests
{
    private static PermissionService CreatePermissionService()
        => new(new FakeUserRepository(), new MemoryCache(new MemoryCacheOptions()));

    [Fact]
    public async Task CreateAsync_WithNewName_CreatesRole()
    {
        var roles = new FakeRoleRepository();
        var sut = new RoleService(roles, CreatePermissionService(), new FakeUserRepository());

        var result = await sut.CreateAsync("Aussendienst");

        Assert.True(result.Success);
        Assert.Equal("Aussendienst", result.Role!.Name);
        var all = await roles.GetAllAsync();
        Assert.Single(all);
    }

    [Fact]
    public async Task CreateAsync_WithExistingName_Fails()
    {
        var roles = new FakeRoleRepository(new Role { Name = "Aussendienst" });
        var sut = new RoleService(roles, CreatePermissionService(), new FakeUserRepository());

        var result = await sut.CreateAsync("Aussendienst");

        Assert.False(result.Success);
        Assert.Equal(CreateRoleFailureReason.NameTaken, result.FailureReason);
    }

    [Fact]
    public async Task CreateAsync_WithEmptyName_Fails()
    {
        var sut = new RoleService(new FakeRoleRepository(), CreatePermissionService(), new FakeUserRepository());

        var result = await sut.CreateAsync("   ");

        Assert.False(result.Success);
        Assert.Equal(CreateRoleFailureReason.NameRequired, result.FailureReason);
    }

    [Fact]
    public async Task UpdatePermissionsAsync_ReplacesExistingPermissions()
    {
        var role = new Role { Name = "Disposition" };
        role.RolePermissions.Add(new RolePermission { RoleId = role.Id, Role = role, Module = ModuleType.Employees, Action = PermissionAction.View });
        var roles = new FakeRoleRepository(role);
        var sut = new RoleService(roles, CreatePermissionService(), new FakeUserRepository());

        var result = await sut.UpdatePermissionsAsync(role.Id, new[] { (ModuleType.Invoices, PermissionAction.Edit, PermissionScope.All) });

        Assert.True(result.Success);
        var updated = await roles.GetByIdWithPermissionsAsync(role.Id);
        var permission = Assert.Single(updated!.RolePermissions);
        Assert.Equal(ModuleType.Invoices, permission.Module);
        Assert.Equal(PermissionAction.Edit, permission.Action);
        Assert.Equal(PermissionScope.All, permission.Scope);
    }

    [Fact]
    public async Task UpdatePermissionsAsync_WithOwnScope_PersistsScope()
    {
        var role = new Role { Name = "Außendienst" };
        var roles = new FakeRoleRepository(role);
        var sut = new RoleService(roles, CreatePermissionService(), new FakeUserRepository());

        var result = await sut.UpdatePermissionsAsync(role.Id, new[] { (ModuleType.Employees, PermissionAction.View, PermissionScope.Own) });

        Assert.True(result.Success);
        var updated = await roles.GetByIdWithPermissionsAsync(role.Id);
        var permission = Assert.Single(updated!.RolePermissions);
        Assert.Equal(PermissionScope.Own, permission.Scope);
    }

    [Fact]
    public async Task UpdatePermissionsAsync_UnknownRole_Fails()
    {
        var sut = new RoleService(new FakeRoleRepository(), CreatePermissionService(), new FakeUserRepository());

        var result = await sut.UpdatePermissionsAsync(Guid.NewGuid(), new[] { (ModuleType.Employees, PermissionAction.View, PermissionScope.All) });

        Assert.False(result.Success);
        Assert.Equal(UpdateRolePermissionsFailureReason.RoleNotFound, result.FailureReason);
    }

    [Fact]
    public async Task DeleteAsync_UnassignedRole_DeletesIt()
    {
        var role = new Role { Name = "Recruiting" };
        var roles = new FakeRoleRepository(role);
        var sut = new RoleService(roles, CreatePermissionService(), new FakeUserRepository());

        var result = await sut.DeleteAsync(role.Id);

        Assert.True(result.Success);
        Assert.Empty(await roles.GetAllAsync());
    }

    [Fact]
    public async Task DeleteAsync_RoleStillAssignedToUser_Fails()
    {
        var role = new Role { Name = "Recruiting" };
        var roles = new FakeRoleRepository(role);
        var user = new User { Username = "sascha", RoleId = role.Id };
        var sut = new RoleService(roles, CreatePermissionService(), new FakeUserRepository(user));

        var result = await sut.DeleteAsync(role.Id);

        Assert.False(result.Success);
        Assert.Equal(DeleteRoleFailureReason.InUse, result.FailureReason);
        Assert.Equal(1, result.AssignedUserCount);
        Assert.Single(await roles.GetAllAsync());
    }

    [Fact]
    public async Task DeleteAsync_UnknownRole_Fails()
    {
        var sut = new RoleService(new FakeRoleRepository(), CreatePermissionService(), new FakeUserRepository());

        var result = await sut.DeleteAsync(Guid.NewGuid());

        Assert.False(result.Success);
        Assert.Equal(DeleteRoleFailureReason.RoleNotFound, result.FailureReason);
    }
}
