using ErsatzTV.Application.Jellyfin;
using ErsatzTV.Core;
using ErsatzTV.Core.Domain;
using ErsatzTV.Core.Interfaces.Jellyfin;
using ErsatzTV.Core.Interfaces.Locking;
using ErsatzTV.Core.Interfaces.Repositories;
using ErsatzTV.Core.Interfaces.Search;
using LanguageExt;
using NSubstitute;
using NSubstitute.ExceptionExtensions;
using NUnit.Framework;
using Shouldly;
using Unit = LanguageExt.Unit;

namespace ErsatzTV.Tests.Application.Jellyfin;

[TestFixture]
public class DisconnectJellyfinHandlerTests
{
    private IMediaSourceRepository _mediaSourceRepository = null!;
    private IJellyfinSecretStore _jellyfinSecretStore = null!;
    private IEntityLocker _entityLocker = null!;
    private ISearchIndex _searchIndex = null!;
    private DisconnectJellyfinHandler _handler = null!;

    [SetUp]
    public void SetUp()
    {
        _mediaSourceRepository = Substitute.For<IMediaSourceRepository>();
        _jellyfinSecretStore = Substitute.For<IJellyfinSecretStore>();
        _entityLocker = Substitute.For<IEntityLocker>();
        _searchIndex = Substitute.For<ISearchIndex>();
        _handler = new DisconnectJellyfinHandler(
            _mediaSourceRepository,
            _jellyfinSecretStore,
            _entityLocker,
            _searchIndex);
    }

    [Test]
    public async Task Handle_Should_Release_Lock_On_Success()
    {
        _mediaSourceRepository.DeleteAllJellyfin().Returns(new List<int>());

        Either<BaseError, Unit> result = await _handler.Handle(new DisconnectJellyfin(), CancellationToken.None);

        result.IsRight.ShouldBeTrue();
        _entityLocker.Received(1).UnlockRemoteMediaSource<JellyfinMediaSource>();
    }

    [Test]
    public async Task Handle_Should_Release_Lock_When_Repository_Delete_Throws()
    {
        _mediaSourceRepository.DeleteAllJellyfin().ThrowsAsync(new InvalidOperationException("db exploded"));

        await Should.ThrowAsync<InvalidOperationException>(
            async () => await _handler.Handle(new DisconnectJellyfin(), CancellationToken.None));

        _entityLocker.Received(1).UnlockRemoteMediaSource<JellyfinMediaSource>();
    }

    [Test]
    public async Task Handle_Should_Release_Lock_When_SearchIndex_RemoveItems_Throws()
    {
        _mediaSourceRepository.DeleteAllJellyfin().Returns([1, 2]);
        _searchIndex.RemoveItems(Arg.Any<IEnumerable<int>>()).ThrowsAsync(new InvalidOperationException("index down"));

        await Should.ThrowAsync<InvalidOperationException>(
            async () => await _handler.Handle(new DisconnectJellyfin(), CancellationToken.None));

        _entityLocker.Received(1).UnlockRemoteMediaSource<JellyfinMediaSource>();
    }

    [Test]
    public async Task Handle_Should_Release_Lock_When_SecretStore_DeleteAll_Throws()
    {
        _mediaSourceRepository.DeleteAllJellyfin().Returns(new List<int>());
        _jellyfinSecretStore.DeleteAll().ThrowsAsync(new InvalidOperationException("secret store down"));

        await Should.ThrowAsync<InvalidOperationException>(
            async () => await _handler.Handle(new DisconnectJellyfin(), CancellationToken.None));

        _entityLocker.Received(1).UnlockRemoteMediaSource<JellyfinMediaSource>();
    }
}
