using System.Reflection;
using ErsatzTV.Application.ChannelTemplates;
using ErsatzTV.Controllers.Api;
using ErsatzTV.Controllers.Api.Requests;
using ErsatzTV.Core;
using ErsatzTV.Core.Api.ChannelTemplates;
using ErsatzTV.Core.Domain;
using ErsatzTV.Core.Errors;
using ErsatzTV.Core.Scheduling;
using LanguageExt;
using MediatR;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Routing;
using NSubstitute;
using NUnit.Framework;
using Shouldly;
using static LanguageExt.Prelude;
using Unit = LanguageExt.Unit;

namespace ErsatzTV.Tests.Controllers;

[TestFixture]
public class ChannelTemplateControllerTests
{
    private ChannelTemplateController _controller = null!;
    private IMediator _mediator = null!;

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

    [Test]
    public void Controller_Should_Expose_Named_Rest_Routes()
    {
        ShouldHaveActionRoute(
            nameof(ChannelTemplateController.GetAll),
            "GET",
            "/api/v1/channel-templates",
            "GetChannelTemplates");
        ShouldHaveActionRoute(
            nameof(ChannelTemplateController.GetDefault),
            "GET",
            "/api/v1/channel-templates/default",
            "GetDefaultChannelTemplate");
        ShouldHaveActionRoute(
            nameof(ChannelTemplateController.SetDefault),
            "PUT",
            "/api/v1/channel-templates/default/{id:int}",
            "SetDefaultChannelTemplate");
        ShouldHaveActionRoute(
            nameof(ChannelTemplateController.GetById),
            "GET",
            "/api/v1/channel-templates/{id:int}",
            "GetChannelTemplateById");
        ShouldHaveActionRoute(
            nameof(ChannelTemplateController.Create),
            "POST",
            "/api/v1/channel-templates",
            "CreateChannelTemplate");
        ShouldHaveActionRoute(
            nameof(ChannelTemplateController.Update),
            "PUT",
            "/api/v1/channel-templates/{id:int}",
            "UpdateChannelTemplate");
        ShouldHaveActionRoute(
            nameof(ChannelTemplateController.Delete),
            "DELETE",
            "/api/v1/channel-templates/{id:int}",
            "DeleteChannelTemplate");
    }

    [Test]
    public async Task GetAll_Should_Return_ChannelTemplates()
    {
        List<ChannelTemplateResponseModel> models = [MakeResponse(1, "Standard", isDefault: true)];
        _mediator.Send(Arg.Any<GetAllChannelTemplates>(), Arg.Any<CancellationToken>())
            .Returns(models);

        List<ChannelTemplateResponseModel> result = await _controller.GetAll(CancellationToken.None);

        result.ShouldBe(models);
    }

    [Test]
    public async Task GetDefault_Should_Return_404_When_Default_Missing()
    {
        _mediator.Send(Arg.Any<GetDefaultChannelTemplate>(), Arg.Any<CancellationToken>())
            .Returns(Option<ChannelTemplateResponseModel>.None);

        IActionResult result = await _controller.GetDefault(CancellationToken.None);

        result.ShouldBeOfType<NotFoundObjectResult>();
    }

    [Test]
    public async Task Create_Should_Return_201_With_Location_And_Body()
    {
        ChannelTemplateResponseModel vm = MakeResponse(7, "Custom");
        _mediator.Send(Arg.Any<CreateChannelTemplate>(), Arg.Any<CancellationToken>())
            .Returns(Right<BaseError, ChannelTemplateResponseModel>(vm));

        IActionResult result = await _controller.Create(MakeCreateRequest("Custom"), CancellationToken.None);

        var created = result.ShouldBeOfType<CreatedResult>();
        created.Location.ShouldBe("/api/v1/channel-templates/7");
        created.Value.ShouldBe(vm);
    }

    [Test]
    public async Task Create_Should_Map_Request_To_Command()
    {
        _mediator.Send(Arg.Any<CreateChannelTemplate>(), Arg.Any<CancellationToken>())
            .Returns(Right<BaseError, ChannelTemplateResponseModel>(MakeResponse(7, "Custom")));

        await _controller.Create(MakeCreateRequest("Custom"), CancellationToken.None);

        await _mediator.Received(1).Send(
            Arg.Is<CreateChannelTemplate>(c =>
                c.Name == "Custom" &&
                c.FFmpegProfileId == 1 &&
                c.PreRollFillerId == 2 &&
                c.MidRollFillerId == 3 &&
                c.PostRollFillerId == 4 &&
                c.ShuffleScheduleItems),
            Arg.Any<CancellationToken>());
    }

    [Test]
    public async Task Update_Should_Return_404_For_NotFoundError()
    {
        _mediator.Send(Arg.Any<UpdateChannelTemplate>(), Arg.Any<CancellationToken>())
            .Returns(Left<BaseError, ChannelTemplateResponseModel>(new NotFoundError("missing")));

        IActionResult result = await _controller.Update(99, MakeUpdateRequest("Missing"), CancellationToken.None);

        result.ShouldBeOfType<NotFoundObjectResult>();
    }

    [Test]
    public async Task SetDefault_Should_Return_200_With_Default_Template()
    {
        ChannelTemplateResponseModel vm = MakeResponse(8, "Default", isDefault: true);
        _mediator.Send(Arg.Any<SetDefaultChannelTemplate>(), Arg.Any<CancellationToken>())
            .Returns(Right<BaseError, ChannelTemplateResponseModel>(vm));

        IActionResult result = await _controller.SetDefault(8, CancellationToken.None);

        result.ShouldBeOfType<OkObjectResult>().Value.ShouldBe(vm);
        await _mediator.Received(1).Send(
            Arg.Is<SetDefaultChannelTemplate>(c => c.ChannelTemplateId == 8),
            Arg.Any<CancellationToken>());
    }

    [Test]
    public async Task Delete_Should_Return_204_On_Success()
    {
        _mediator.Send(Arg.Any<DeleteChannelTemplate>(), Arg.Any<CancellationToken>())
            .Returns(Right<BaseError, Unit>(Unit.Default));

        IActionResult result = await _controller.Delete(9, CancellationToken.None);

        result.ShouldBeOfType<NoContentResult>();
    }

    [Test]
    public async Task Delete_Should_Return_422_When_System_Template()
    {
        _mediator.Send(Arg.Any<DeleteChannelTemplate>(), Arg.Any<CancellationToken>())
            .Returns(Left<BaseError, Unit>(BaseError.New("System templates cannot be deleted.")));

        IActionResult result = await _controller.Delete(9, CancellationToken.None);

        result.ShouldBeOfType<UnprocessableEntityObjectResult>();
    }

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

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

    private static CreateChannelTemplateRequest MakeCreateRequest(string name) =>
        new(
            name,
            "Description",
            1,
            null,
            null,
            2,
            3,
            4,
            ChannelStreamSelectorMode.Default,
            string.Empty,
            string.Empty,
            string.Empty,
            ChannelPlayoutSource.Generated,
            ChannelPlayoutMode.Continuous,
            StreamingMode.TransportStreamHybrid,
            string.Empty,
            ChannelSubtitleMode.None,
            ChannelMusicVideoCreditsMode.None,
            string.Empty,
            ChannelSongVideoMode.Default,
            ChannelTranscodeMode.OnDemand,
            ChannelIdleBehavior.StopOnDisconnect,
            true,
            false,
            FixedStartTimeBehavior.Flexible);

    private static UpdateChannelTemplateRequest MakeUpdateRequest(string name) =>
        new(
            name,
            "Description",
            1,
            null,
            null,
            null,
            null,
            null,
            ChannelStreamSelectorMode.Default,
            string.Empty,
            string.Empty,
            string.Empty,
            ChannelPlayoutSource.Generated,
            ChannelPlayoutMode.Continuous,
            StreamingMode.TransportStreamHybrid,
            string.Empty,
            ChannelSubtitleMode.None,
            ChannelMusicVideoCreditsMode.None,
            string.Empty,
            ChannelSongVideoMode.Default,
            ChannelTranscodeMode.OnDemand,
            ChannelIdleBehavior.StopOnDisconnect,
            false,
            false,
            FixedStartTimeBehavior.Flexible);

    private static ChannelTemplateResponseModel MakeResponse(int id, string name, bool isDefault = false) =>
        new(
            id,
            name,
            "Description",
            false,
            isDefault,
            1,
            null,
            null,
            null,
            null,
            null,
            ChannelStreamSelectorMode.Default,
            string.Empty,
            string.Empty,
            string.Empty,
            ChannelPlayoutSource.Generated,
            ChannelPlayoutMode.Continuous,
            StreamingMode.TransportStreamHybrid,
            string.Empty,
            ChannelSubtitleMode.None,
            ChannelMusicVideoCreditsMode.None,
            string.Empty,
            ChannelSongVideoMode.Default,
            ChannelTranscodeMode.OnDemand,
            ChannelIdleBehavior.StopOnDisconnect,
            false,
            false,
            FixedStartTimeBehavior.Flexible);
}
