using ErsatzTV.Application.Configuration;
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.Configuration;

[TestFixture]
public class IptvSettingsHandlerTests
{
    private const string Key = "iptv.base_url";

    private IConfigElementRepository _configElementRepository = null!;

    [SetUp]
    public void SetUp() => _configElementRepository = Substitute.For<IConfigElementRepository>();

    [Test]
    public async Task Get_Returns_Empty_When_Unset()
    {
        _configElementRepository
            .GetValue<string>(Arg.Any<ConfigElementKey>(), Arg.Any<CancellationToken>())
            .Returns(Option<string>.None);

        var handler = new GetIptvSettingsHandler(_configElementRepository);

        IptvSettingsViewModel result = await handler.Handle(new GetIptvSettings(), CancellationToken.None);

        result.BaseUrl.ShouldBe(string.Empty);
    }

    [Test]
    public async Task Get_Returns_Stored_Value()
    {
        _configElementRepository
            .GetValue<string>(Arg.Any<ConfigElementKey>(), Arg.Any<CancellationToken>())
            .Returns(Option<string>.Some("https://tv.example.com/etv"));

        var handler = new GetIptvSettingsHandler(_configElementRepository);

        IptvSettingsViewModel result = await handler.Handle(new GetIptvSettings(), CancellationToken.None);

        result.BaseUrl.ShouldBe("https://tv.example.com/etv");
    }

    [Test]
    public async Task Update_Upserts_Trimmed_Value_When_Valid()
    {
        var handler = new UpdateIptvSettingsHandler(_configElementRepository);

        Either<BaseError, Unit> result = await handler.Handle(
            new UpdateIptvSettings(new IptvSettingsViewModel { BaseUrl = "  https://public.example.com/etv  " }),
            CancellationToken.None);

        result.IsRight.ShouldBeTrue();
        await _configElementRepository.Received(1).Upsert(
            Arg.Is<ConfigElementKey>(k => k.Key == Key),
            "https://public.example.com/etv",
            Arg.Any<CancellationToken>());
        await _configElementRepository.DidNotReceive().Delete(Arg.Any<ConfigElementKey>(), Arg.Any<CancellationToken>());
    }

    [TestCase(null)]
    [TestCase("")]
    [TestCase("   ")]
    public async Task Update_Clears_Setting_When_Blank(string blank)
    {
        var handler = new UpdateIptvSettingsHandler(_configElementRepository);

        Either<BaseError, Unit> result = await handler.Handle(
            new UpdateIptvSettings(new IptvSettingsViewModel { BaseUrl = blank }),
            CancellationToken.None);

        result.IsRight.ShouldBeTrue();
        await _configElementRepository.Received(1).Delete(
            Arg.Is<ConfigElementKey>(k => k.Key == Key),
            Arg.Any<CancellationToken>());
        await _configElementRepository.DidNotReceive().Upsert(
            Arg.Any<ConfigElementKey>(),
            Arg.Any<string>(),
            Arg.Any<CancellationToken>());
    }

    [TestCase("not a url")]
    [TestCase("ftp://tv.example.com")]
    [TestCase("http://user:pass@tv.example.com")]
    [TestCase("http://tv.example.com?foo=bar")]
    public async Task Update_Returns_Error_And_Persists_Nothing_When_Invalid(string invalid)
    {
        var handler = new UpdateIptvSettingsHandler(_configElementRepository);

        Either<BaseError, Unit> result = await handler.Handle(
            new UpdateIptvSettings(new IptvSettingsViewModel { BaseUrl = invalid }),
            CancellationToken.None);

        result.IsLeft.ShouldBeTrue();
        await _configElementRepository.DidNotReceive().Upsert(
            Arg.Any<ConfigElementKey>(),
            Arg.Any<string>(),
            Arg.Any<CancellationToken>());
        await _configElementRepository.DidNotReceive().Delete(Arg.Any<ConfigElementKey>(), Arg.Any<CancellationToken>());
    }
}
