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

namespace ErsatzTV.Controllers.Api;

[ApiController]
public class DecoController(IMediator mediator) : ControllerBase
{
    [HttpGet("/api/v1/decos/groups", Name = "GetDecoGroups")]
    [Tags("Decos")]
    [EndpointSummary("Get all deco groups")]
    [EndpointGroupName("general")]
    [ProducesResponseType(typeof(List<DecoGroupResponseModel>), StatusCodes.Status200OK)]
    public async Task<List<DecoGroupResponseModel>> GetGroups(CancellationToken cancellationToken)
    {
        List<DecoGroupViewModel> groups = await mediator.Send(new GetAllDecoGroups(), cancellationToken);
        return groups.Map(ProjectToResponseModel).ToList();
    }

    [HttpPost("/api/v1/decos/groups")]
    [Tags("Decos")]
    [EndpointSummary("Create a deco group")]
    [EndpointGroupName("general")]
    [ProducesResponseType(typeof(DecoGroupResponseModel), StatusCodes.Status201Created)]
    [ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status422UnprocessableEntity)]
    public async Task<IActionResult> CreateGroup(
        [Required][FromBody] CreateDecoGroupRequest request,
        CancellationToken cancellationToken)
    {
        Either<BaseError, DecoGroupViewModel> result =
            await mediator.Send(request.ToCommand(), cancellationToken);
        return result.ToCreatedResult(
            vm => $"/api/v1/decos/groups/{vm.Id}",
            vm => ProjectToResponseModel(vm));
    }

    [HttpDelete("/api/v1/decos/groups/{id:int}")]
    [Tags("Decos")]
    [EndpointSummary("Delete a deco group")]
    [EndpointDescription(
        "Deletes the deco group. The database cascade removes every deco in the group (and each deco's break " +
        "content and watermark/graphics-element assignments); any playout that used a removed deco has its " +
        "default deco cleared.")]
    [EndpointGroupName("general")]
    [ProducesResponseType(StatusCodes.Status204NoContent)]
    [ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status404NotFound)]
    public async Task<IActionResult> DeleteGroup(int id, CancellationToken cancellationToken)
    {
        List<DecoGroupViewModel> groups = await mediator.Send(new GetAllDecoGroups(), cancellationToken);
        if (groups.All(g => g.Id != id))
        {
            return ApiResults.NotFoundProblem();
        }

        Option<BaseError> result = await mediator.Send(new DeleteDecoGroup(id), cancellationToken);
        return result.Match<IActionResult>(
            Some: error => error.ToErrorResult(),
            None: () => new NoContentResult());
    }

    [HttpGet("/api/v1/decos")]
    [Tags("Decos")]
    [EndpointSummary("Get all decos")]
    [EndpointDescription("Returns every deco as a flat list, ordered by group name then deco name.")]
    [EndpointGroupName("general")]
    [ProducesResponseType(typeof(List<DecoListItemResponseModel>), StatusCodes.Status200OK)]
    public async Task<List<DecoListItemResponseModel>> GetAll(CancellationToken cancellationToken)
    {
        List<DecoGroupViewModel> groups = await mediator.Send(new GetAllDecoGroups(), cancellationToken);
        var result = new List<DecoListItemResponseModel>();
        foreach (DecoGroupViewModel group in groups)
        {
            List<DecoViewModel> decos = await mediator.Send(new GetDecosByDecoGroupId(group.Id), cancellationToken);
            result.AddRange(decos.Map(d => new DecoListItemResponseModel(d.Id, d.DecoGroupId, d.DecoGroupName, d.Name)));
        }

        return result;
    }

    [HttpGet("/api/v1/decos/{id:int}", Name = "GetDecoById")]
    [Tags("Decos")]
    [EndpointSummary("Get a deco by id")]
    [EndpointGroupName("general")]
    [ProducesResponseType(typeof(DecoResponseModel), StatusCodes.Status200OK)]
    [ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status404NotFound)]
    public async Task<IActionResult> GetById(int id, CancellationToken cancellationToken)
    {
        Option<DecoViewModel> result = await mediator.Send(new GetDecoById(id), cancellationToken);
        return result.Map(ProjectToResponseModel).ToGetResult();
    }

    [HttpPost("/api/v1/decos")]
    [Tags("Decos")]
    [EndpointSummary("Create a deco")]
    [EndpointDescription("Creates a deco in the given deco group. The deco inherits everything by default.")]
    [EndpointGroupName("general")]
    [ProducesResponseType(typeof(DecoResponseModel), StatusCodes.Status201Created)]
    [ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status404NotFound)]
    [ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status422UnprocessableEntity)]
    public async Task<IActionResult> Create(
        [Required][FromBody] CreateDecoRequest request,
        CancellationToken cancellationToken)
    {
        Either<BaseError, DecoViewModel> result = await mediator.Send(request.ToCommand(), cancellationToken);
        return result.ToCreatedResult(
            vm => $"/api/v1/decos/{vm.Id}",
            vm => ProjectToResponseModel(vm));
    }

    [HttpDelete("/api/v1/decos/{id:int}")]
    [Tags("Decos")]
    [EndpointSummary("Delete a deco")]
    [EndpointDescription(
        "Deletes the deco. Any playout that used it as its default deco has that default cleared.")]
    [EndpointGroupName("general")]
    [ProducesResponseType(StatusCodes.Status204NoContent)]
    [ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status404NotFound)]
    public async Task<IActionResult> Delete(int id, CancellationToken cancellationToken)
    {
        Option<DecoViewModel> existing = await mediator.Send(new GetDecoById(id), cancellationToken);
        if (existing.IsNone)
        {
            return ApiResults.NotFoundProblem();
        }

        Option<BaseError> result = await mediator.Send(new DeleteDeco(id), cancellationToken);
        return result.Match<IActionResult>(
            Some: error => error.ToErrorResult(),
            None: () => new NoContentResult());
    }

    [HttpPut("/api/v1/decos/{id:int}")]
    [Tags("Decos")]
    [EndpointSummary("Replace a deco")]
    [EndpointDescription(
        "Replaces the full state of the deco (name and all six sections). Send the entire desired state, including " +
        "unchanged sections. Merge is only valid for the watermark and graphics-element modes. Break content allows " +
        "at most one item per placement.")]
    [EndpointGroupName("general")]
    [ProducesResponseType(typeof(DecoResponseModel), StatusCodes.Status200OK)]
    [ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status404NotFound)]
    [ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status422UnprocessableEntity)]
    public async Task<IActionResult> Replace(
        int id,
        [Required][FromBody] ReplaceDecoRequest request,
        CancellationToken cancellationToken)
    {
        Option<DecoViewModel> maybeDeco = await mediator.Send(new GetDecoById(id), cancellationToken);
        if (maybeDeco.IsNone)
        {
            return ApiResults.NotFoundProblem();
        }

        int decoGroupId = maybeDeco.Map(d => d.DecoGroupId).IfNone(0);

        Either<BaseError, UpdateDeco> commandOrError = request.ToCommand(decoGroupId, id);
        return await commandOrError.Match(
            Left: error => Task.FromResult(error.ToErrorResult()),
            Right: async command =>
            {
                Either<BaseError, Unit> result = await mediator.Send(command, cancellationToken);
                return await result.Match(
                    Left: error => Task.FromResult(error.ToErrorResult()),
                    Right: async _ =>
                    {
                        Option<DecoViewModel> refreshed = await mediator.Send(new GetDecoById(id), cancellationToken);
                        return refreshed.Match(
                            Some: vm => (IActionResult)new OkObjectResult(ProjectToResponseModel(vm)),
                            None: () => ApiResults.NotFoundProblem());
                    });
            });
    }

    private static DecoGroupResponseModel ProjectToResponseModel(DecoGroupViewModel vm) =>
        new(vm.Id, vm.Name, vm.DecoCount);

    private static DecoResponseModel ProjectToResponseModel(DecoViewModel vm) =>
        new(
            vm.Id,
            vm.DecoGroupId,
            vm.DecoGroupName,
            vm.Name,
            vm.WatermarkMode,
            vm.Watermarks.Map(w => w.Id).ToList(),
            vm.UseWatermarkDuringFiller,
            vm.GraphicsElementsMode,
            vm.GraphicsElements.Map(g => g.Id).ToList(),
            vm.UseGraphicsElementsDuringFiller,
            vm.BreakContentMode,
            vm.BreakContent.Map(ProjectToResponseModel).ToList(),
            vm.DefaultFillerMode,
            vm.DefaultFillerCollectionType,
            vm.DefaultFillerCollection?.Id,
            vm.DefaultFillerMediaItem?.MediaItemId,
            vm.DefaultFillerMultiCollection?.Id,
            vm.DefaultFillerSmartCollection?.Id,
            vm.DefaultFillerCollection?.Name
            ?? vm.DefaultFillerMediaItem?.Name
            ?? vm.DefaultFillerMultiCollection?.Name
            ?? vm.DefaultFillerSmartCollection?.Name,
            vm.DefaultFillerTrimToFit,
            vm.DeadAirFallbackMode,
            vm.DeadAirFallbackCollectionType,
            vm.DeadAirFallbackCollection?.Id,
            vm.DeadAirFallbackMediaItem?.MediaItemId,
            vm.DeadAirFallbackMultiCollection?.Id,
            vm.DeadAirFallbackSmartCollection?.Id,
            vm.DeadAirFallbackCollection?.Name
            ?? vm.DeadAirFallbackMediaItem?.Name
            ?? vm.DeadAirFallbackMultiCollection?.Name
            ?? vm.DeadAirFallbackSmartCollection?.Name);

    private static DecoBreakContentResponseModel ProjectToResponseModel(DecoBreakContentViewModel vm) =>
        new(
            vm.Id,
            vm.CollectionType,
            vm.Collection?.Id,
            vm.MediaItem?.MediaItemId,
            vm.MultiCollection?.Id,
            vm.SmartCollection?.Id,
            vm.Playlist?.Id,
            vm.Playlist?.PlaylistGroupId,
            vm.Collection?.Name
            ?? vm.MediaItem?.Name
            ?? vm.MultiCollection?.Name
            ?? vm.SmartCollection?.Name
            ?? vm.Playlist?.Name,
            vm.Placement);
}
