using ErsatzTV.Application.Emby;
using ErsatzTV.Core;
using ErsatzTV.Core.Domain;
using ErsatzTV.Core.Interfaces.Repositories;
using LanguageExt;
using NSubstitute;
using NUnit.Framework;
using Shouldly;
using Unit = LanguageExt.Unit;

namespace ErsatzTV.Tests.Application.Emby;

[TestFixture]
public class UpdateEmbyPathReplacementsHandlerTests
{
    private IMediaSourceRepository _mediaSourceRepository = null!;
    private UpdateEmbyPathReplacementsHandler _handler = null!;

    [SetUp]
    public void SetUp()
    {
        _mediaSourceRepository = Substitute.For<IMediaSourceRepository>();
        _handler = new UpdateEmbyPathReplacementsHandler(_mediaSourceRepository);
    }

    [Test]
    public async Task Handle_Should_Return_422_When_Source_Does_Not_Exist()
    {
        _mediaSourceRepository.GetEmby(99, Arg.Any<CancellationToken>()).Returns(Option<EmbyMediaSource>.None);

        Either<BaseError, Unit> result = await _handler.Handle(
            new UpdateEmbyPathReplacements(99, [new EmbyPathReplacementItem(0, "/emby", "/local")]),
            CancellationToken.None);

        result.IsLeft.ShouldBeTrue();
        await _mediaSourceRepository.DidNotReceive().UpdatePathReplacements(
            Arg.Any<int>(),
            Arg.Any<List<EmbyPathReplacement>>(),
            Arg.Any<List<EmbyPathReplacement>>(),
            Arg.Any<List<EmbyPathReplacement>>());
    }

    [Test]
    public async Task Handle_Should_Return_422_For_Cross_Source_Id_And_Perform_No_Mutation()
    {
        var source = new EmbyMediaSource
        {
            Id = 1,
            PathReplacements = [new EmbyPathReplacement { Id = 1, EmbyPath = "/emby1", LocalPath = "/l1" }]
        };
        _mediaSourceRepository.GetEmby(1, Arg.Any<CancellationToken>()).Returns(Option<EmbyMediaSource>.Some(source));

        // id 999 belongs to some other source, not this one
        Either<BaseError, Unit> result = await _handler.Handle(
            new UpdateEmbyPathReplacements(1, [new EmbyPathReplacementItem(999, "/emby", "/local")]),
            CancellationToken.None);

        result.IsLeft.ShouldBeTrue();
        await _mediaSourceRepository.DidNotReceive().UpdatePathReplacements(
            Arg.Any<int>(),
            Arg.Any<List<EmbyPathReplacement>>(),
            Arg.Any<List<EmbyPathReplacement>>(),
            Arg.Any<List<EmbyPathReplacement>>());
    }

    [TestCase("", "/local")]
    [TestCase("/emby", "")]
    [TestCase(" ", " ")]
    public async Task Handle_Should_Return_422_For_Blank_Paths(string embyPath, string localPath)
    {
        var source = new EmbyMediaSource { Id = 1, PathReplacements = [] };
        _mediaSourceRepository.GetEmby(1, Arg.Any<CancellationToken>()).Returns(Option<EmbyMediaSource>.Some(source));

        Either<BaseError, Unit> result = await _handler.Handle(
            new UpdateEmbyPathReplacements(1, [new EmbyPathReplacementItem(0, embyPath, localPath)]),
            CancellationToken.None);

        result.IsLeft.ShouldBeTrue();
        await _mediaSourceRepository.DidNotReceive().UpdatePathReplacements(
            Arg.Any<int>(),
            Arg.Any<List<EmbyPathReplacement>>(),
            Arg.Any<List<EmbyPathReplacement>>(),
            Arg.Any<List<EmbyPathReplacement>>());
    }

    [Test]
    public async Task Handle_Should_Return_422_For_Null_Items_List_Defense()
    {
        var source = new EmbyMediaSource { Id = 1, PathReplacements = [] };
        _mediaSourceRepository.GetEmby(1, Arg.Any<CancellationToken>()).Returns(Option<EmbyMediaSource>.Some(source));

        Either<BaseError, Unit> result = await _handler.Handle(
            new UpdateEmbyPathReplacements(1, [null!]),
            CancellationToken.None);

        result.IsLeft.ShouldBeTrue();
    }

    [Test]
    public async Task Handle_Should_Merge_Add_Update_Delete_On_Valid_Request()
    {
        var source = new EmbyMediaSource
        {
            Id = 1,
            PathReplacements =
            [
                new EmbyPathReplacement { Id = 1, EmbyPath = "/old", LocalPath = "/old-local" },
                new EmbyPathReplacement { Id = 2, EmbyPath = "/gone", LocalPath = "/gone-local" }
            ]
        };
        _mediaSourceRepository.GetEmby(1, Arg.Any<CancellationToken>()).Returns(Option<EmbyMediaSource>.Some(source));
        _mediaSourceRepository.UpdatePathReplacements(
                Arg.Any<int>(),
                Arg.Any<List<EmbyPathReplacement>>(),
                Arg.Any<List<EmbyPathReplacement>>(),
                Arg.Any<List<EmbyPathReplacement>>())
            .Returns(Unit.Default);

        Either<BaseError, Unit> result = await _handler.Handle(
            new UpdateEmbyPathReplacements(
                1,
                [
                    new EmbyPathReplacementItem(1, "/updated", "/updated-local"), // update
                    new EmbyPathReplacementItem(0, "/new", "/new-local") // add
                    // id 2 is absent -> delete
                ]),
            CancellationToken.None);

        result.IsRight.ShouldBeTrue();
        await _mediaSourceRepository.Received(1).UpdatePathReplacements(
            1,
            Arg.Is<List<EmbyPathReplacement>>(l => l.Count == 1 && l[0].EmbyPath == "/new"),
            Arg.Is<List<EmbyPathReplacement>>(l => l.Count == 1 && l[0].Id == 1 && l[0].EmbyPath == "/updated"),
            Arg.Is<List<EmbyPathReplacement>>(l => l.Count == 1 && l[0].Id == 2));
    }
}
