using OmsorgCore.Application.Services;
using OmsorgCore.Domain.Entities;
using OmsorgCore.Tests.TestDoubles;

namespace OmsorgCore.Tests.Services;

public class SessionAdminServiceTests
{
    private static User CreateUser(string username) => new()
    {
        Username = username,
        PasswordHash = "hashed:secret",
        IsActive = true,
        RoleId = Guid.NewGuid(),
        Role = new Role { Name = "Geschaeftsfuehrung" },
        SecurityStamp = Guid.NewGuid()
    };

    private static RefreshToken CreateActiveToken(User user, string tokenHash) => new()
    {
        UserId = user.Id,
        User = user,
        TokenHash = tokenHash,
        ExpiresAt = DateTime.UtcNow.AddDays(30)
    };

    [Fact]
    public async Task GetActiveSessionsAsync_ReturnsOnlyActiveTokens()
    {
        var user = CreateUser("sabina");
        var refreshTokens = new FakeRefreshTokenRepository();
        var activeToken = CreateActiveToken(user, "hash-active");
        await refreshTokens.AddAsync(activeToken);

        var sut = new SessionAdminService(refreshTokens, new FakeUserRepository(user));

        var sessions = await sut.GetActiveSessionsAsync();

        var session = Assert.Single(sessions);
        Assert.Equal(activeToken.Id, session.Id);
        Assert.Equal("sabina", session.Username);
    }

    [Fact]
    public async Task RevokeSessionAsync_RevokesTokenAndBumpsOnlyThatUsersStamp()
    {
        var userA = CreateUser("sabina");
        var userB = CreateUser("malik");
        var stampBeforeA = userA.SecurityStamp;
        var stampBeforeB = userB.SecurityStamp;

        var refreshTokens = new FakeRefreshTokenRepository();
        var tokenA = CreateActiveToken(userA, "hash-a");
        await refreshTokens.AddAsync(tokenA);
        var tokenB = CreateActiveToken(userB, "hash-b");
        await refreshTokens.AddAsync(tokenB);

        var sut = new SessionAdminService(refreshTokens, new FakeUserRepository(userA, userB));

        await sut.RevokeSessionAsync(tokenA.Id);

        Assert.NotNull(tokenA.RevokedAt);
        Assert.Null(tokenB.RevokedAt);
        Assert.NotEqual(stampBeforeA, userA.SecurityStamp);
        Assert.Equal(stampBeforeB, userB.SecurityStamp);
    }

    [Fact]
    public async Task RevokeSessionAsync_WithUnknownId_DoesNothing()
    {
        var sut = new SessionAdminService(new FakeRefreshTokenRepository(), new FakeUserRepository());

        await sut.RevokeSessionAsync(Guid.NewGuid());

        var sessions = await sut.GetActiveSessionsAsync();
        Assert.Empty(sessions);
    }

    [Fact]
    public async Task RevokeAllSessionsAsync_RevokesEveryTokenAndBumpsEveryUsersStamp()
    {
        var userA = CreateUser("sabina");
        var userB = CreateUser("malik");
        var stampBeforeA = userA.SecurityStamp;
        var stampBeforeB = userB.SecurityStamp;

        var refreshTokens = new FakeRefreshTokenRepository();
        var tokenA = CreateActiveToken(userA, "hash-a");
        await refreshTokens.AddAsync(tokenA);
        var tokenB = CreateActiveToken(userB, "hash-b");
        await refreshTokens.AddAsync(tokenB);

        var sut = new SessionAdminService(refreshTokens, new FakeUserRepository(userA, userB));

        await sut.RevokeAllSessionsAsync();

        Assert.NotNull(tokenA.RevokedAt);
        Assert.NotNull(tokenB.RevokedAt);
        Assert.NotEqual(stampBeforeA, userA.SecurityStamp);
        Assert.NotEqual(stampBeforeB, userB.SecurityStamp);

        var sessions = await sut.GetActiveSessionsAsync();
        Assert.Empty(sessions);
    }
}
