using ErsatzTV.Core;
using ErsatzTV.Core.Domain;
using ErsatzTV.Core.Interfaces.Images;
using ErsatzTV.Core.Interfaces.Streaming;
using ErsatzTV.Infrastructure.Images;
using LanguageExt;
using NSubstitute;
using NUnit.Framework;
using Shouldly;
using SixLabors.ImageSharp;
using SixLabors.ImageSharp.Formats.Png;
using SixLabors.ImageSharp.PixelFormats;
using static LanguageExt.Prelude;
using Image = SixLabors.ImageSharp.Image;

namespace ErsatzTV.Infrastructure.Tests.Images;

[TestFixture]
public class RemoteLogoCacherTests
{
    private static readonly Uri Uri = new("https://example.com/logo.png");

    [Test]
    public async Task Should_Fetch_Validate_And_Cache_Returning_The_Name()
    {
        MemoryStream png = await RealPng(64, 64);
        var fetcher = Substitute.For<IRemoteImageFetcher>();
        fetcher.Fetch(Uri, Arg.Any<CancellationToken>()).Returns(png);
        var validator = Substitute.For<IRemoteImageValidator>();
        validator.Validate(png, Uri, Arg.Any<CancellationToken>()).Returns(Task.CompletedTask);
        var cache = Substitute.For<IImageCache>();
        cache.SaveArtworkToCache(Arg.Any<Stream>(), ArtworkKind.Logo).Returns(Right<BaseError, string>("abc123"));

        var cacher = new RemoteLogoCacher(fetcher, validator, cache);
        Either<BaseError, string> result = await cacher.CacheFromUrl(Uri, CancellationToken.None);

        result.IsRight.ShouldBeTrue();
        result.IfRight(name => name.ShouldBe("abc123"));
    }

    [Test]
    public async Task Should_Return_Left_When_The_Fetch_Throws()
    {
        var fetcher = Substitute.For<IRemoteImageFetcher>();
        fetcher.Fetch(Uri, Arg.Any<CancellationToken>()).Returns<Stream>(_ => throw new TimeoutException("timed out"));
        var cacher = new RemoteLogoCacher(fetcher, Substitute.For<IRemoteImageValidator>(), Substitute.For<IImageCache>());

        Either<BaseError, string> result = await cacher.CacheFromUrl(Uri, CancellationToken.None);

        result.IsLeft.ShouldBeTrue();
        result.IfLeft(e => e.Value.ShouldContain("timed out"));
    }

    [Test]
    public async Task Should_Return_Left_When_Validation_Rejects_A_Bomb()
    {
        MemoryStream png = await RealPng(64, 64);
        var fetcher = Substitute.For<IRemoteImageFetcher>();
        fetcher.Fetch(Uri, Arg.Any<CancellationToken>()).Returns(png);
        var validator = Substitute.For<IRemoteImageValidator>();
        validator.Validate(png, Uri, Arg.Any<CancellationToken>())
            .Returns<Task>(_ => throw new InvalidOperationException("over the 50000000 pixel limit"));
        var cacher = new RemoteLogoCacher(fetcher, validator, Substitute.For<IImageCache>());

        Either<BaseError, string> result = await cacher.CacheFromUrl(Uri, CancellationToken.None);

        result.IsLeft.ShouldBeTrue();
        result.IfLeft(e => e.Value.ShouldContain("pixel limit"));
    }

    private static async Task<MemoryStream> RealPng(int w, int h)
    {
        using var img = new Image<Rgba32>(w, h);
        var ms = new MemoryStream();
        await img.SaveAsync(ms, new PngEncoder());
        ms.Position = 0;
        return ms;
    }
}
