using JSMR.Application.Enums;
using JSMR.Application.Integrations.DLSite.Models.ReleasedWorks;
using JSMR.Application.Integrations.DLSite.Ports;
using JSMR.Application.Scanning.Contracts;
using JSMR.Infrastructure.Scanning;
using NSubstitute;
using Shouldly;

namespace JSMR.Tests.Unit;

public class ReleasedWorksProviderTests
{
    private readonly IDLSiteClient _dlsiteClient = Substitute.For<IDLSiteClient>();

    [Fact]
    public async Task GetReleasedWorksAsync_WhenNoSalesDates_ReturnsEmptyAndDoesNotCallClient()
    {
        VoiceWorkScanResult scanResult = new(
            Works:
            [
                new DLSiteWork
                {
                    ProductId = "RJ001",
                    SalesDate = null,
                    ProductName = "",
                    MakerId = "",
                    Maker = "",
                    ImageUrl = "",
                    SmallImageUrl = ""
                }
            ],
            EndOfResults: false
        );

        ReleasedWorksProvider provider = new(_dlsiteClient);

        ReleasedWorksCollection result = await provider.GetReleasedWorksAsync(scanResult, TestContext.Current.CancellationToken);

        result.ShouldBeEmpty();

        await _dlsiteClient.DidNotReceiveWithAnyArgs().GetReleasedWorksAsync(default!, TestContext.Current.CancellationToken);
    }

    [Fact]
    public async Task GetReleasedWorksAsync_WhenRangeIsUnder60Days_CallsClientOnce()
    {
        VoiceWorkScanResult scanResult = new(
            Works:
            [
                new DLSiteWork
                {
                    ProductId = "RJ001",
                    SalesDate = new DateOnly(2024, 1, 10),
                    ProductName = "",
                    MakerId = "",
                    Maker = "",
                    ImageUrl = "",
                    SmallImageUrl = ""
                },
                new DLSiteWork
                {
                    ProductId = "RJ002",
                    SalesDate = new DateOnly(2024, 1, 20),
                    ProductName = "",
                    MakerId = "",
                    Maker = "",
                    ImageUrl = "",
                    SmallImageUrl = ""
                }
            ],
            EndOfResults: false
        );

        ReleasedWorksCollection apiResult = new()
        {
            ["RJ001"] = new ReleasedWork
            {
                ProductId = "RJ001",
                Title = "English title 1",
                Description = "Description",
                MaskedTitle = "English title 1",
                MaskedDescription = "Description",
            },
            ["RJ002"] = new ReleasedWork
            {
                ProductId = "RJ002",
                Title = "English title 2",
                Description = "Description",
                MaskedTitle = "English title 2",
                MaskedDescription = "Description",
            }
        };

        _dlsiteClient
            .GetReleasedWorksAsync(Arg.Any<ReleasedWorksRequest>(), Arg.Any<CancellationToken>())
            .Returns(apiResult);

        ReleasedWorksProvider provider = new(_dlsiteClient);

        ReleasedWorksCollection result =
            await provider.GetReleasedWorksAsync(scanResult, TestContext.Current.CancellationToken);

        result.Keys.ShouldBe(["RJ001", "RJ002"], ignoreOrder: true);

        await _dlsiteClient.Received(1).GetReleasedWorksAsync(
            Arg.Is<ReleasedWorksRequest>(x =>
                x.Locale == Locale.English &&
                x.Date == new DateOnly(2024, 1, 20) &&
                x.Period == 11),
            Arg.Any<CancellationToken>());
    }

    [Fact]
    public async Task GetReleasedWorksAsync_WhenRangeExceeds60Days_SplitsIntoMultipleRequests()
    {
        VoiceWorkScanResult scanResult = new(
            Works:
            [
                new DLSiteWork
                {
                    ProductId = "RJ001",
                    SalesDate = new DateOnly(2024, 1, 1),
                    ProductName = "",
                    MakerId = "",
                    Maker = "",
                    ImageUrl = "",
                    SmallImageUrl = ""
                },
                new DLSiteWork
                {
                    ProductId = "RJ002",
                    SalesDate = new DateOnly(2024, 3, 5),
                    ProductName = "",
                    MakerId = "",
                    Maker = "",
                    ImageUrl = "",
                    SmallImageUrl = ""
                }
            ],
            EndOfResults: false
        );

        _dlsiteClient
            .GetReleasedWorksAsync(Arg.Any<ReleasedWorksRequest>(), Arg.Any<CancellationToken>())
            .Returns([]);

        ReleasedWorksProvider provider = new(_dlsiteClient);

        await provider.GetReleasedWorksAsync(scanResult, TestContext.Current.CancellationToken);

        await _dlsiteClient.Received(1).GetReleasedWorksAsync(
            Arg.Is<ReleasedWorksRequest>(x =>
                x.Date == new DateOnly(2024, 2, 29) &&
                x.Period == 60),
            Arg.Any<CancellationToken>());

        await _dlsiteClient.Received(1).GetReleasedWorksAsync(
            Arg.Is<ReleasedWorksRequest>(x =>
                x.Date == new DateOnly(2024, 3, 5) &&
                x.Period == 5),
            Arg.Any<CancellationToken>());

        await _dlsiteClient.Received(2).GetReleasedWorksAsync(
            Arg.Any<ReleasedWorksRequest>(),
            Arg.Any<CancellationToken>());
    }

    [Fact]
    public async Task GetReleasedWorksAsync_FiltersOutProductsNotInScanResult()
    {
        VoiceWorkScanResult scanResult = new(
            Works:
            [
                new DLSiteWork
                {
                    ProductId = "RJ001",
                    SalesDate = new DateOnly(2024, 1, 10),
                    ProductName = "",
                    MakerId = "",
                    Maker = "",
                    ImageUrl = "",
                    SmallImageUrl = ""
                }
            ],
            EndOfResults: false
        );

        ReleasedWorksCollection apiResult = new()
        {
            ["RJ001"] = new ReleasedWork
            {
                ProductId = "RJ001",
                Title = "Keep me",
                Description = "Description",
                MaskedTitle = "Keep me",
                MaskedDescription = "Description",
            },
            ["RJ999"] = new ReleasedWork
            {
                ProductId = "RJ999",
                Title = "Ignore me",
                Description = "Description",
                MaskedTitle = "Ignore me",
                MaskedDescription = "Description",
            },
        };

        _dlsiteClient
            .GetReleasedWorksAsync(Arg.Any<ReleasedWorksRequest>(), Arg.Any<CancellationToken>())
            .Returns(apiResult);

        ReleasedWorksProvider provider = new(_dlsiteClient);

        ReleasedWorksCollection result =
            await provider.GetReleasedWorksAsync(scanResult, TestContext.Current.CancellationToken);

        result.Keys.ShouldBe(["RJ001"]);
    }

    [Fact]
    public async Task GetReleasedWorksAsync_WhenSameProductReturnedTwice_KeepsFirstResult()
    {
        VoiceWorkScanResult scanResult = new(
            Works:
            [
                new DLSiteWork
                {
                    ProductId = "RJ001",
                    SalesDate = new DateOnly(2024, 1, 1),
                    ProductName = "",
                    MakerId = "",
                    Maker = "",
                    ImageUrl = "",
                    SmallImageUrl = ""
                },
                new DLSiteWork
                {
                    ProductId = "RJ002",
                    SalesDate = new DateOnly(2024, 3, 5),
                    ProductName = "",
                    MakerId = "",
                    Maker = "",
                    ImageUrl = "",
                    SmallImageUrl = ""
                }
            ],
            EndOfResults: false
        );

        _dlsiteClient
            .GetReleasedWorksAsync(
                Arg.Is<ReleasedWorksRequest>(x => x.Period == 60),
                Arg.Any<CancellationToken>())
            .Returns(new ReleasedWorksCollection
            {
                ["RJ001"] = new ReleasedWork
                {
                    ProductId = "RJ001",
                    Title = "First",
                    Description = "Description",
                    MaskedTitle = "First",
                    MaskedDescription = "Description",
                }
            });

        _dlsiteClient
            .GetReleasedWorksAsync(
                Arg.Is<ReleasedWorksRequest>(x => x.Period == 5),
                Arg.Any<CancellationToken>())
            .Returns(new ReleasedWorksCollection
            {
                ["RJ001"] = new ReleasedWork
                {
                    ProductId = "RJ001",
                    Title = "Second",
                    Description = "Description",
                    MaskedTitle = "Second",
                    MaskedDescription = "Description",
                }
            });

        ReleasedWorksProvider provider = new(_dlsiteClient);

        ReleasedWorksCollection result = await provider.GetReleasedWorksAsync(scanResult, TestContext.Current.CancellationToken);

        result["RJ001"].Title.ShouldBe("First");
    }
}