using System.ComponentModel.DataAnnotations;
using ErsatzTV.Application.MediaCollections;
using ErsatzTV.Controllers.Api.Requests;
using ErsatzTV.Core;
using ErsatzTV.Core.Api.MediaCollections;
using ErsatzTV.Extensions;
using MediatR;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;

namespace ErsatzTV.Controllers.Api;

[ApiController]
public class MultiCollectionController(IMediator mediator) : ControllerBase
{
    private const int MaxPageSize = 100;

    [HttpGet("/api/v1/multi-collections", Name = "GetMultiCollections")]
    [Tags("Multi Collections")]
    [EndpointSummary("Get all multi collections (paged)")]
    [EndpointGroupName("general")]
    [ProducesResponseType(typeof(PagedMultiCollectionsResponseModel), StatusCodes.Status200OK)]
    public async Task<PagedMultiCollectionsResponseModel> GetAll(
        [FromQuery] string query = "",
        [FromQuery] int pageNum = 0,
        [FromQuery] int pageSize = 100,
        CancellationToken cancellationToken = default)
    {
        int clampedPageNum = Math.Max(0, pageNum);
        int clampedPageSize = Math.Clamp(pageSize, 1, MaxPageSize);

        PagedMultiCollectionsViewModel result = await mediator.Send(
            new GetPagedMultiCollections(query ?? string.Empty, clampedPageNum, clampedPageSize),
            cancellationToken);

        return new PagedMultiCollectionsResponseModel(
            result.TotalCount,
            result.Page.Map(ProjectToResponseModel).ToList());
    }

    [HttpGet("/api/v1/multi-collections/{id:int}", Name = "GetMultiCollectionById")]
    [Tags("Multi Collections")]
    [EndpointSummary("Get a multi collection by id")]
    [EndpointGroupName("general")]
    [ProducesResponseType(typeof(MultiCollectionResponseModel), StatusCodes.Status200OK)]
    [ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status404NotFound)]
    public async Task<IActionResult> GetById(int id, CancellationToken cancellationToken)
    {
        Option<MultiCollectionViewModel> result =
            await mediator.Send(new GetMultiCollectionById(id), cancellationToken);

        // This by-id GET is the editor's load endpoint: emit the concurrency ETag (issue #253).
        foreach (MultiCollectionViewModel vm in result)
        {
            ConcurrencyHeaders.SetETag(Response, vm.Version);
        }

        return result.Map(ProjectToResponseModel).ToGetResult();
    }

    [HttpPost("/api/v1/multi-collections")]
    [Tags("Multi Collections")]
    [EndpointSummary("Create a multi collection")]
    [EndpointGroupName("general")]
    [ProducesResponseType(typeof(MultiCollectionResponseModel), StatusCodes.Status201Created)]
    [ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status404NotFound)]
    [ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status422UnprocessableEntity)]
    public async Task<IActionResult> Create(
        [Required][FromBody] CreateMultiCollectionRequest request,
        CancellationToken cancellationToken)
    {
        Either<BaseError, MultiCollectionViewModel> result =
            await mediator.Send(request.ToCommand(), cancellationToken);
        return result.ToCreatedResult(
            vm => $"/api/v1/multi-collections/{vm.Id}",
            ProjectToResponseModel);
    }

    [HttpPut("/api/v1/multi-collections/{id:int}")]
    [Tags("Multi Collections")]
    [EndpointSummary("Update a multi collection")]
    [EndpointGroupName("general")]
    [ProducesResponseType(typeof(MultiCollectionResponseModel), StatusCodes.Status200OK)]
    [ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status400BadRequest)]
    [ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status404NotFound)]
    [ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status412PreconditionFailed)]
    [ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status422UnprocessableEntity)]
    public async Task<IActionResult> Update(
        int id,
        [Required][FromBody] UpdateMultiCollectionRequest request,
        CancellationToken cancellationToken)
    {
        IfMatchCondition ifMatch = ConcurrencyHeaders.ParseIfMatch(Request);
        if (ifMatch.Kind is IfMatchKind.Malformed)
        {
            return ConcurrencyHeaders.MalformedIfMatchProblem();
        }

        Either<BaseError, Unit> result =
            await mediator.Send(request.ToCommand(id, ifMatch.ExpectedVersions), cancellationToken);
        return await result.Match(
            Left: error => Task.FromResult(error.ToErrorResult()),
            Right: async _ =>
            {
                Option<MultiCollectionViewModel> multiCollection =
                    await mediator.Send(new GetMultiCollectionById(id), cancellationToken);
                return multiCollection.Match(
                    Some: vm =>
                    {
                        // Emit the new ETag so a same-tab second save doesn't 412 against its own write (#253).
                        ConcurrencyHeaders.SetETag(Response, vm.Version);
                        return (IActionResult)new OkObjectResult(ProjectToResponseModel(vm));
                    },
                    None: () => ApiResults.NotFoundProblem());
            });
    }

    [HttpDelete("/api/v1/multi-collections/{id:int}")]
    [Tags("Multi Collections")]
    [EndpointSummary("Delete a multi collection")]
    [EndpointGroupName("general")]
    [ProducesResponseType(StatusCodes.Status204NoContent)]
    [ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status404NotFound)]
    [ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status422UnprocessableEntity)]
    public async Task<IActionResult> Delete(int id, CancellationToken cancellationToken)
    {
        Either<BaseError, Unit> result = await mediator.Send(new DeleteMultiCollection(id), cancellationToken);
        return result.ToDeletedResult();
    }

    private static MultiCollectionResponseModel ProjectToResponseModel(MultiCollectionViewModel vm)
    {
        List<MultiCollectionItemResponseModel> items = vm.Items
            .Map(i => new MultiCollectionItemResponseModel(
                i.Collection.Id,
                null,
                i.Collection.Name,
                i.ScheduleAsGroup,
                i.PlaybackOrder,
                i.Weight))
            .Concat(vm.SmartItems.Map(i => new MultiCollectionItemResponseModel(
                null,
                i.SmartCollection.Id,
                i.SmartCollection.Name,
                i.ScheduleAsGroup,
                i.PlaybackOrder,
                i.Weight)))
            .ToList();

        return new MultiCollectionResponseModel(vm.Id, vm.Name, items);
    }
}
