using ErsatzTV.Application.Auth;
using ErsatzTV.Core;
using ErsatzTV.Core.Domain;
using ErsatzTV.Core.Interfaces.Repositories;
using ErsatzTV.Infrastructure.Data.Repositories;
using ErsatzTV.Tests.Support;
using LanguageExt;
using NUnit.Framework;
using Shouldly;

namespace ErsatzTV.Tests.Application.Auth;

[TestFixture]
public class RotateLocalAdminSecurityStampHandlerTests
{
    private InMemoryTvContext _db = null!;
    private IConfigElementRepository _configElementRepository = null!;
    private ILocalPasswordHasher _passwordHasher = null!;

    [SetUp]
    public async Task SetUp()
    {
        _db = await InMemoryTvContext.CreateAsync();
        _configElementRepository = new ConfigElementRepository(_db.Factory);
        _passwordHasher = new LocalPasswordHasher();
    }

    [TearDown]
    public async Task TearDown() => await _db.DisposeAsync();

    private RotateLocalAdminSecurityStampHandler MakeHandler() => new(_db.Factory);

    [Test]
    public async Task Handle_Should_Rotate_The_Stamp_When_Configured()
    {
        // Arrange: claim an admin so a stamp exists.
        Either<BaseError, LocalAdminPrincipal> claim = await new ClaimLocalAdminHandler(_db.Factory, _passwordHasher)
            .Handle(new ClaimLocalAdmin("admin", "supersecret"), CancellationToken.None);
        claim.IsRight.ShouldBeTrue();

        Option<string> before =
            await _configElementRepository.GetValue<string>(ConfigElementKey.AuthSecurityStamp, CancellationToken.None);
        string originalStamp = before.Match(s => s, () => throw new ShouldAssertException("expected a stamp"));

        // Act
        await MakeHandler().Handle(new RotateLocalAdminSecurityStamp(), CancellationToken.None);

        // Assert: the stamp changed (all outstanding sessions carrying the old stamp are now stale).
        Option<string> after =
            await _configElementRepository.GetValue<string>(ConfigElementKey.AuthSecurityStamp, CancellationToken.None);
        string rotatedStamp = after.Match(s => s, () => throw new ShouldAssertException("expected a stamp"));
        rotatedStamp.ShouldNotBe(originalStamp);
        rotatedStamp.ShouldNotBeNullOrEmpty();
    }

    [Test]
    public async Task Handle_Should_Be_A_No_Op_When_Unconfigured()
    {
        await MakeHandler().Handle(new RotateLocalAdminSecurityStamp(), CancellationToken.None);

        Option<string> stamp =
            await _configElementRepository.GetValue<string>(ConfigElementKey.AuthSecurityStamp, CancellationToken.None);
        stamp.IsNone.ShouldBeTrue();
    }
}
