using LiteCqrs;
using TeleWave.Api.Common;
using TeleWave.Application.Programming.Groups;
using TeleWave.Application.Programming.Groups.AddGroupElements;
using TeleWave.Application.Programming.Groups.CreateGroup;
using TeleWave.Application.Programming.Groups.DeleteGroup;
using TeleWave.Application.Programming.Groups.ExcludeGroupElement;
using TeleWave.Application.Programming.Groups.FindGroupCandidates;
using TeleWave.Application.Programming.Groups.GetGroup;
using TeleWave.Application.Programming.Groups.ListGroups;
using TeleWave.Application.Programming.Groups.RemoveGroupItem;
using TeleWave.Application.Programming.Groups.ReorderGroup;
using TeleWave.Application.Programming.Groups.SetGroupItemWeight;
using TeleWave.Application.Programming.Groups.Suggest;
using TeleWave.Application.Programming.Groups.UpdateGroup;
using TeleWave.Domain.Programming;
using TeleWave.Infrastructure.Identity;

namespace TeleWave.Api.Endpoints;

public static class GroupEndpoints
{
    public static IEndpointRouteBuilder MapGroupEndpoints(this IEndpointRouteBuilder app)
    {
        var admin = app.MapGroup("/api/admin/groups")
            .WithTags("Admin.Groups")
            .RequireAuthorization(policy => policy.RequireRole(RoleNames.Admin));

        admin.MapGet("", ListGroups).Produces<IReadOnlyList<GroupSummaryDto>>();
        admin.MapGet("/{id:guid}", GetGroup).Produces<GroupDto>();
        admin.MapPost("", CreateGroup).Produces<CreatedIdResponse>(StatusCodes.Status201Created);
        admin.MapPut("/{id:guid}", UpdateGroup).Produces(StatusCodes.Status204NoContent);
        admin.MapDelete("/{id:guid}", DeleteGroup).Produces(StatusCodes.Status204NoContent);

        // Подбор по правилу набора: правило можно передать в теле, чтобы крутить его до сохранения.
        admin
            .MapPost("/{id:guid}/candidates", FindCandidates)
            .Produces<IReadOnlyList<GroupCandidateDto>>();

        // Предложения: что имеет смысл завести группой при нынешней библиотеке.
        admin.MapGet("/suggestions", Suggestions).Produces<IReadOnlyList<GroupSuggestionDto>>();
        admin
            .MapPost("/suggestions", CreateFromSuggestion)
            .Produces<CreatedIdResponse>(StatusCodes.Status201Created);

        admin.MapPost("/{id:guid}/items", AddElements).Produces<AddedCountResponse>();
        admin
            .MapDelete("/{id:guid}/items/{itemId:guid}", RemoveItem)
            .Produces(StatusCodes.Status204NoContent);
        admin
            .MapPut("/{id:guid}/items/{itemId:guid}/weight", SetWeight)
            .Produces(StatusCodes.Status204NoContent);
        admin.MapPut("/{id:guid}/order", Reorder).Produces(StatusCodes.Status204NoContent);

        // Поправки к вычисленному составу динамической группы.
        admin.MapPost("/{id:guid}/exclusions", Exclude).Produces(StatusCodes.Status204NoContent);

        return app;
    }

    private static async Task<IResult> ListGroups(
        ISender sender,
        CancellationToken cancellationToken
    )
    {
        var result = await sender.Send(new ListGroupsQuery(), cancellationToken);
        return Results.Ok(result);
    }

    private static async Task<IResult> GetGroup(
        Guid id,
        ISender sender,
        CancellationToken cancellationToken
    )
    {
        var result = await sender.Send(new GetGroupQuery(id), cancellationToken);
        return result.ToHttpResult();
    }

    private static async Task<IResult> CreateGroup(
        CreateGroupCommand command,
        ISender sender,
        CancellationToken cancellationToken
    )
    {
        var result = await sender.Send(command, cancellationToken);
        return result.IsSuccess
            ? Results.Created(
                $"/api/admin/groups/{result.Value}",
                new CreatedIdResponse(result.Value)
            )
            : result.ToHttpResult();
    }

    private static async Task<IResult> Suggestions(
        ISender sender,
        CancellationToken cancellationToken
    )
    {
        var result = await sender.Send(new SuggestGroupsQuery(), cancellationToken);
        return Results.Ok(result);
    }

    private static async Task<IResult> CreateFromSuggestion(
        CreateGroupFromSuggestionBody body,
        ISender sender,
        CancellationToken cancellationToken
    )
    {
        var result = await sender.Send(
            new CreateGroupFromSuggestionCommand(body.Key),
            cancellationToken
        );
        return result.IsSuccess
            ? Results.Created(
                $"/api/admin/groups/{result.Value}",
                new CreatedIdResponse(result.Value)
            )
            : result.ToHttpResult();
    }

    private static async Task<IResult> UpdateGroup(
        Guid id,
        UpdateGroupBody body,
        ISender sender,
        CancellationToken cancellationToken
    )
    {
        var result = await sender.Send(
            new UpdateGroupCommand(id, body.Name, body.Description, body.Filter, body.Mode),
            cancellationToken
        );
        return result.ToHttpResult();
    }

    private static async Task<IResult> DeleteGroup(
        Guid id,
        ISender sender,
        CancellationToken cancellationToken
    )
    {
        var result = await sender.Send(new DeleteGroupCommand(id), cancellationToken);
        return result.ToHttpResult();
    }

    private static async Task<IResult> FindCandidates(
        Guid id,
        FindCandidatesBody? body,
        ISender sender,
        CancellationToken cancellationToken
    )
    {
        var result = await sender.Send(
            new FindGroupCandidatesQuery(id, body?.Filter),
            cancellationToken
        );
        return result.ToHttpResult();
    }

    private static async Task<IResult> AddElements(
        Guid id,
        AddGroupElementsBody body,
        ISender sender,
        CancellationToken cancellationToken
    )
    {
        var result = await sender.Send(
            new AddGroupElementsCommand(id, body.Elements),
            cancellationToken
        );
        return result.IsSuccess
            ? Results.Ok(new AddedCountResponse(result.Value))
            : result.ToHttpResult();
    }

    private static async Task<IResult> RemoveItem(
        Guid id,
        Guid itemId,
        ISender sender,
        CancellationToken cancellationToken
    )
    {
        var result = await sender.Send(new RemoveGroupItemCommand(id, itemId), cancellationToken);
        return result.ToHttpResult();
    }

    private static async Task<IResult> SetWeight(
        Guid id,
        Guid itemId,
        GroupItemWeightBody body,
        ISender sender,
        CancellationToken cancellationToken
    )
    {
        var result = await sender.Send(
            new SetGroupItemWeightCommand(id, itemId, body.Weight),
            cancellationToken
        );
        return result.ToHttpResult();
    }

    private static async Task<IResult> Exclude(
        Guid id,
        ExcludeElementBody body,
        ISender sender,
        CancellationToken cancellationToken
    )
    {
        var result = await sender.Send(
            new ExcludeGroupElementCommand(id, body.ElementKind, body.ElementId, body.Excluded),
            cancellationToken
        );
        return result.ToHttpResult();
    }

    private static async Task<IResult> Reorder(
        Guid id,
        ReorderGroupBody body,
        ISender sender,
        CancellationToken cancellationToken
    )
    {
        var result = await sender.Send(
            new ReorderGroupCommand(id, body.ItemIdsInOrder),
            cancellationToken
        );
        return result.ToHttpResult();
    }
}

public sealed record UpdateGroupBody(
    string Name,
    string? Description,
    GroupFilter? Filter,
    GroupMode? Mode = null
);

/// <summary>Исключить элемент из динамической группы либо вернуть его правилу.</summary>
public sealed record ExcludeElementBody(
    GroupElementKind ElementKind,
    Guid ElementId,
    bool Excluded
);

public sealed record FindCandidatesBody(GroupFilter? Filter);

public sealed record CreateGroupFromSuggestionBody(string Key);

public sealed record AddGroupElementsBody(IReadOnlyList<GroupElementRef> Elements);

public sealed record GroupItemWeightBody(int Weight);

public sealed record ReorderGroupBody(IReadOnlyList<Guid> ItemIdsInOrder);

/// <summary>Сколько позиций реально добавлено (уже входящие в группу пропускаются).</summary>
public sealed record AddedCountResponse(int Added);
