using System.Reflection;
using ErsatzTV.Application.Images;
using ErsatzTV.Controllers.Api;
using ErsatzTV.Controllers.Api.Requests;
using ErsatzTV.Core.Api.Images;
using LanguageExt;
using MediatR;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Routing;
using NSubstitute;
using NUnit.Framework;
using Shouldly;

namespace ErsatzTV.Tests.Controllers;

[TestFixture]
public class ImagesControllerTests
{
    private ImagesController _controller = null!;
    private IMediator _mediator = null!;

    [SetUp]
    public void SetUp()
    {
        _mediator = Substitute.For<IMediator>();
        _controller = new ImagesController(_mediator);
    }

    [Test]
    public void Controller_Should_Expose_Idiomatic_Rest_Routes()
    {
        ShouldHaveActionRoute(nameof(ImagesController.GetFolders), "GET", "/api/v1/images/folders");
        ShouldHaveActionRoute(
            nameof(ImagesController.UpdateDuration),
            "PUT",
            "/api/v1/images/folders/{id:int}/duration");
    }

    [Test]
    public async Task GetFolders_Should_Map_Duration_Option_To_Nullable()
    {
        _mediator.Send(Arg.Any<GetImageFolders>(), Arg.Any<CancellationToken>())
            .Returns(
            [
                new ImageFolderViewModel(1, "Root", "/images", 2, 5, Option<double>.None),
                new ImageFolderViewModel(2, "Child", "/images/child", 0, 3, Option<double>.Some(4.5))
            ]);

        List<ImageFolderResponseModel> result = await _controller.GetFolders(null, CancellationToken.None);

        result.Count.ShouldBe(2);
        result[0].DurationSeconds.ShouldBeNull();
        result[1].DurationSeconds.ShouldBe(4.5);
        result[1].Name.ShouldBe("Child");
    }

    [Test]
    public async Task GetFolders_Should_Pass_None_When_ParentId_Omitted()
    {
        _mediator.Send(Arg.Any<GetImageFolders>(), Arg.Any<CancellationToken>()).Returns([]);

        await _controller.GetFolders(null, CancellationToken.None);

        await _mediator.Received().Send(
            Arg.Is<GetImageFolders>(q => q.LibraryFolderId.IsNone),
            Arg.Any<CancellationToken>());
    }

    [Test]
    public async Task GetFolders_Should_Pass_Some_When_ParentId_Given()
    {
        _mediator.Send(Arg.Any<GetImageFolders>(), Arg.Any<CancellationToken>()).Returns([]);

        await _controller.GetFolders(42, CancellationToken.None);

        await _mediator.Received().Send(
            Arg.Is<GetImageFolders>(q => q.LibraryFolderId == Option<int>.Some(42)),
            Arg.Any<CancellationToken>());
    }

    [Test]
    public async Task UpdateDuration_Should_Return_400_For_Non_Positive_Duration()
    {
        IActionResult result = await _controller.UpdateDuration(
            1,
            new UpdateImageFolderDurationRequest(0),
            CancellationToken.None);

        result.ShouldBeOfType<BadRequestObjectResult>().StatusCode.ShouldBe(400);
        await _mediator.DidNotReceive().Send(Arg.Any<ImageFolderExists>(), Arg.Any<CancellationToken>());
    }

    [Test]
    public async Task UpdateDuration_Should_Return_404_When_Folder_Missing()
    {
        _mediator.Send(Arg.Any<ImageFolderExists>(), Arg.Any<CancellationToken>()).Returns(false);

        IActionResult result = await _controller.UpdateDuration(
            1,
            new UpdateImageFolderDurationRequest(3.0),
            CancellationToken.None);

        result.ShouldBeOfType<NotFoundObjectResult>().StatusCode.ShouldBe(404);
        await _mediator.DidNotReceive().Send(Arg.Any<UpdateImageFolderDuration>(), Arg.Any<CancellationToken>());
    }

    [Test]
    public async Task UpdateDuration_Should_Return_200_And_Update_When_Valid()
    {
        _mediator.Send(Arg.Any<ImageFolderExists>(), Arg.Any<CancellationToken>()).Returns(true);
        _mediator.Send(Arg.Any<UpdateImageFolderDuration>(), Arg.Any<CancellationToken>()).Returns(3.0);

        IActionResult result = await _controller.UpdateDuration(
            1,
            new UpdateImageFolderDurationRequest(3.0),
            CancellationToken.None);

        var ok = result.ShouldBeOfType<OkObjectResult>();
        ok.Value.ShouldBeOfType<UpdateImageFolderDurationResponseModel>().DurationSeconds.ShouldBe(3.0);
    }

    [Test]
    public async Task UpdateDuration_Should_Allow_Null_To_Clear()
    {
        _mediator.Send(Arg.Any<ImageFolderExists>(), Arg.Any<CancellationToken>()).Returns(true);
        _mediator.Send(Arg.Any<UpdateImageFolderDuration>(), Arg.Any<CancellationToken>())
            .Returns((double?)null);

        IActionResult result = await _controller.UpdateDuration(
            1,
            new UpdateImageFolderDurationRequest(null),
            CancellationToken.None);

        result.ShouldBeOfType<OkObjectResult>()
            .Value.ShouldBeOfType<UpdateImageFolderDurationResponseModel>().DurationSeconds.ShouldBeNull();
    }

    private static void ShouldHaveActionRoute(string actionName, string httpMethod, string route)
    {
        MethodInfo action = typeof(ImagesController).GetMethod(actionName)
            ?? throw new AssertionException($"Missing action {actionName}");

        HttpMethodAttribute attribute = action.GetCustomAttributes<HttpMethodAttribute>(inherit: true).Single();
        attribute.HttpMethods.ShouldContain(httpMethod);
        attribute.Template.ShouldBe(route);
    }
}
