using ErsatzTV.Core;
using ErsatzTV.Core.Errors;
using ErsatzTV.Extensions;
using LanguageExt;
using static LanguageExt.Prelude;
using Microsoft.AspNetCore.Mvc;
using NUnit.Framework;
using Shouldly;

namespace ErsatzTV.Tests.Extensions;

[TestFixture]
public class ApiResultsTests
{
    [Test]
    public void ToErrorResult_Should_Map_NotFoundError_To_404()
    {
        IActionResult result = new NotFoundError("missing").ToErrorResult();
        result.ShouldBeOfType<NotFoundObjectResult>().StatusCode.ShouldBe(404);
    }

    [Test]
    public void ToErrorResult_Should_Map_PreconditionFailedError_To_412()
    {
        IActionResult result = new PreconditionFailedError("stale").ToErrorResult();
        result.ShouldBeOfType<ObjectResult>().StatusCode.ShouldBe(412);
    }

    [Test]
    public void ToErrorResult_Should_Return_ProblemDetails_For_NotFoundError()
    {
        IActionResult result = new NotFoundError("missing").ToErrorResult();

        var notFound = result.ShouldBeOfType<NotFoundObjectResult>();
        var problemDetails = notFound.Value.ShouldBeOfType<ProblemDetails>();
        problemDetails.Status.ShouldBe(404);
        problemDetails.Title.ShouldBe("Resource not found");
        problemDetails.Detail.ShouldBe("missing");
    }

    [Test]
    public void ToErrorResult_Should_Map_LockedError_To_409()
    {
        IActionResult result = new LockedError("locked").ToErrorResult();
        result.ShouldBeOfType<ConflictObjectResult>().StatusCode.ShouldBe(409);
    }

    [Test]
    public void ToErrorResult_Should_Return_ProblemDetails_For_LockedError()
    {
        IActionResult result = new LockedError("locked").ToErrorResult();

        var conflict = result.ShouldBeOfType<ConflictObjectResult>();
        var problemDetails = conflict.Value.ShouldBeOfType<ProblemDetails>();
        problemDetails.Status.ShouldBe(409);
        problemDetails.Detail.ShouldBe("locked");
    }

    [Test]
    public void ToErrorResult_Should_Map_Other_Error_To_422()
    {
        IActionResult result = BaseError.New("bad").ToErrorResult();
        result.ShouldBeOfType<UnprocessableEntityObjectResult>().StatusCode.ShouldBe(422);
    }

    [Test]
    public void ToErrorResult_Should_Return_ProblemDetails_For_Validation_Error()
    {
        IActionResult result = BaseError.New("bad").ToErrorResult();

        var unprocessable = result.ShouldBeOfType<UnprocessableEntityObjectResult>();
        var problemDetails = unprocessable.Value.ShouldBeOfType<ProblemDetails>();
        problemDetails.Status.ShouldBe(422);
        problemDetails.Title.ShouldBe("Validation failed");
        problemDetails.Detail.ShouldBe("bad");
    }

    [Test]
    public void ToCreatedResult_Should_Return_201_With_Location_And_Body()
    {
        Either<BaseError, int> either = Right<BaseError, int>(5);

        IActionResult result = either.ToCreatedResult(id => $"/api/v1/channels/{id}", id => $"body-{id}");

        var created = result.ShouldBeOfType<CreatedResult>();
        created.StatusCode.ShouldBe(201);
        created.Location.ShouldBe("/api/v1/channels/5");
        created.Value.ShouldBe("body-5");
    }

    [Test]
    public void ToCreatedResult_Should_Map_NotFoundError_To_404()
    {
        Either<BaseError, int> either = Left<BaseError, int>(new NotFoundError("nope"));

        IActionResult result = either.ToCreatedResult(id => $"/api/v1/channels/{id}", id => id);

        result.ShouldBeOfType<NotFoundObjectResult>();
    }

    [Test]
    public void ToUpdatedResult_Should_Return_200_On_Right()
    {
        Either<BaseError, string> either = Right<BaseError, string>("vm");
        IActionResult result = either.ToUpdatedResult();

        var ok = result.ShouldBeOfType<OkObjectResult>();
        ok.Value.ShouldBe("vm");
    }

    [Test]
    public void ToUpdatedResult_Should_Return_404_For_NotFoundError()
    {
        Either<BaseError, string> either = Left<BaseError, string>(new NotFoundError("missing"));
        IActionResult result = either.ToUpdatedResult();

        result.ShouldBeOfType<NotFoundObjectResult>();
    }

    [Test]
    public void ToUpdatedResult_Should_Return_422_For_Other_Error()
    {
        Either<BaseError, string> either = Left<BaseError, string>(BaseError.New("bad"));
        IActionResult result = either.ToUpdatedResult();

        result.ShouldBeOfType<UnprocessableEntityObjectResult>();
    }

    [Test]
    public void ToDeletedResult_Should_Return_204_On_Right()
    {
        Either<BaseError, Unit> either = Right<BaseError, Unit>(Unit.Default);
        IActionResult result = either.ToDeletedResult();

        result.ShouldBeOfType<NoContentResult>().StatusCode.ShouldBe(204);
    }

    [Test]
    public void ToDeletedResult_Should_Return_404_For_NotFoundError()
    {
        Either<BaseError, Unit> either = Left<BaseError, Unit>(new NotFoundError("missing"));
        IActionResult result = either.ToDeletedResult();

        result.ShouldBeOfType<NotFoundObjectResult>();
    }

    [Test]
    public void ToGetResult_Should_Return_200_For_Some()
    {
        Option<string> option = "value";
        IActionResult result = option.ToGetResult();

        result.ShouldBeOfType<OkObjectResult>().Value.ShouldBe("value");
    }

    [Test]
    public void ToGetResult_Should_Return_404_For_None()
    {
        Option<string> option = Option<string>.None;
        IActionResult result = option.ToGetResult();

        result.ShouldBeOfType<NotFoundObjectResult>();
    }

    [Test]
    public void ToGetResult_Should_Return_ProblemDetails_For_None()
    {
        Option<string> option = Option<string>.None;
        IActionResult result = option.ToGetResult();

        var notFound = result.ShouldBeOfType<NotFoundObjectResult>();
        var problemDetails = notFound.Value.ShouldBeOfType<ProblemDetails>();
        problemDetails.Status.ShouldBe(404);
        problemDetails.Title.ShouldBe("Resource not found");
    }
}
