using GmRelay.DiscordBot.Infrastructure.Discord;
using GmRelay.DiscordBot.Rendering;
using GmRelay.Shared.Domain;
using GmRelay.Shared.Features.Confirmation.HandleRsvp;
using GmRelay.Shared.Features.Sessions.CreateSession;
using GmRelay.Shared.Platform;
using System.Collections;
using System.Globalization;
using NetCord;
using NetCord.Rest;
using NetCord.Services.ComponentInteractions;

namespace GmRelay.DiscordBot.Features.Sessions;

public sealed class DiscordSessionInteractionModule(
    JoinSessionHandler joinSessionHandler,
    LeaveSessionHandler leaveSessionHandler,
    HandleRsvpHandler rsvpHandler,
    DiscordDeleteSessionHandler deleteSessionHandler,
    DiscordRescheduleVoteHandler voteHandler,
    DiscordInteractionReplyCache interactionReplies,
    ILogger<DiscordSessionInteractionModule> logger) : ComponentInteractionModule<ButtonInteractionContext>
{
    [ComponentInteraction("join_session")]
    public async Task JoinAsync(string sessionId)
    {
        if (!Guid.TryParse(sessionId, out var parsedSessionId))
        {
            await RespondAsync(CreateEphemeralReply("Session button is outdated."));
            return;
        }

        var input = CreateInput(parsedSessionId);
        await RespondAsync(InteractionCallback.DeferredModifyMessage);
        SessionInteractionResult result;
        try
        {
            result = await joinSessionHandler.HandleAsync(
                DiscordSessionInteractionMapper.CreateJoinCommand(input) with { DeferScheduleUpdate = true },
                CancellationToken.None);
        }
        catch (Exception ex)
        {
            logger.LogError(ex, "Failed to handle Discord join interaction for session {SessionId}", parsedSessionId);
            await FollowupEphemeralAsync("Не удалось обработать кнопку.");
            return;
        }

        await CompleteScheduleUpdateResponseAsync(input.InteractionId, result);
    }

    [ComponentInteraction("leave_session")]
    public async Task LeaveAsync(string sessionId)
    {
        if (!Guid.TryParse(sessionId, out var parsedSessionId))
        {
            await RespondAsync(CreateEphemeralReply("Session button is outdated."));
            return;
        }

        var input = CreateInput(parsedSessionId);
        await RespondAsync(InteractionCallback.DeferredModifyMessage);
        SessionInteractionResult result;
        try
        {
            result = await leaveSessionHandler.HandleAsync(
                DiscordSessionInteractionMapper.CreateLeaveCommand(input) with { DeferScheduleUpdate = true },
                CancellationToken.None);
        }
        catch (Exception ex)
        {
            logger.LogError(ex, "Failed to handle Discord leave interaction for session {SessionId}", parsedSessionId);
            await FollowupEphemeralAsync("Не удалось обработать кнопку.");
            return;
        }

        await CompleteScheduleUpdateResponseAsync(input.InteractionId, result);
    }

    [ComponentInteraction("delete_session")]
    public async Task DeleteAsync(string sessionId)
    {
        if (!Guid.TryParse(sessionId, out var parsedSessionId))
        {
            await RespondAsync(CreateEphemeralReply("Session button is outdated."));
            return;
        }

        var input = CreateInput(parsedSessionId);
        var member = Context.User as GuildInteractionUser;
        var resolvedPermissions = member is null ? 0UL : (ulong)member.Permissions;

        await RespondAsync(InteractionCallback.DeferredModifyMessage);
        try
        {
            var result = await deleteSessionHandler.HandleAsync(
                guildId: input.GuildId,
                channelId: input.ChannelId,
                userId: input.UserId,
                resolvedPermissions: resolvedPermissions,
                guildOwnerId: 0,
                sessionId: parsedSessionId,
                CancellationToken.None);

            await CompleteDeleteResponseAsync(result);
        }
        catch (Exception ex)
        {
            logger.LogError(ex, "Failed to handle Discord delete interaction for session {SessionId}", parsedSessionId);
            await FollowupEphemeralAsync("Не удалось удалить сессию.");
        }
    }

    [ComponentInteraction("rsvp")]
    public async Task RsvpAsync(string status, string sessionId)
    {
        if (!Guid.TryParse(sessionId, out var parsedSessionId))
        {
            await RespondAsync(CreateEphemeralReply("Session button is outdated."));
            return;
        }

        var rsvpStatus = status switch
        {
            "confirm" => RsvpStatus.Confirmed,
            "decline" => RsvpStatus.Declined,
            _ => null
        };

        if (rsvpStatus is null)
        {
            await RespondAsync(CreateEphemeralReply("Session button is outdated."));
            return;
        }

        var input = CreateInput(parsedSessionId);
        await RespondAsync(InteractionCallback.DeferredMessage(MessageFlags.Ephemeral));

        try
        {
            await rsvpHandler.HandleAsync(
                new HandleRsvpCommand(
                    parsedSessionId,
                    new PlatformUser(
                        PlatformKind.Discord,
                        Context.User.Id.ToString(CultureInfo.InvariantCulture),
                        string.IsNullOrWhiteSpace(Context.User.GlobalName) ? Context.User.Username : Context.User.GlobalName,
                        Context.User.Username),
                    rsvpStatus,
                    input.InteractionId,
                    new PlatformGroup(
                        PlatformKind.Discord,
                        input.GuildId,
                        input.GuildId,
                        input.ChannelId),
                    new PlatformMessageRef(
                        PlatformKind.Discord,
                        input.GuildId,
                        null,
                        input.MessageId)),
                CancellationToken.None);
        }
        catch (Exception ex)
        {
            logger.LogError(ex, "Failed to handle Discord RSVP interaction for session {SessionId}", parsedSessionId);
            await CompleteResponseAsync("Не удалось обработать кнопку.");
            return;
        }

        await CompleteWithStoredReplyAsync(input.InteractionId);
    }

    [ComponentInteraction("reschedule_vote")]
    public async Task RescheduleVoteAsync(string optionId)
    {
        if (!Guid.TryParse(optionId, out var parsedOptionId))
        {
            await RespondAsync(CreateEphemeralReply("Vote button is outdated."));
            return;
        }

        var input = CreateInput(Guid.Empty); // sessionId not needed for vote routing
        var voteInput = new DiscordRescheduleVoteInput(
            parsedOptionId,
            Context.User.Id,
            Context.Interaction.Id.ToString(System.Globalization.CultureInfo.InvariantCulture),
            input.GuildId,
            input.ChannelId,
            input.MessageId);

        await RespondAsync(InteractionCallback.DeferredMessage(MessageFlags.Ephemeral));

        string replyText;
        try
        {
            replyText = await voteHandler.HandleAsync(voteInput, CancellationToken.None);
        }
        catch (Exception ex)
        {
            logger.LogError(ex, "Failed to handle Discord reschedule vote for option {OptionId}", parsedOptionId);
            await CompleteResponseAsync("Не удалось обработать голос.");
            return;
        }

        await CompleteResponseAsync(replyText);
    }

    private DiscordSessionInteractionInput CreateInput(Guid sessionId)
    {
        var guildId = Context.Interaction.GuildId?.ToString(CultureInfo.InvariantCulture)
            ?? throw new InvalidOperationException("Session buttons can only be used in a guild.");
        var message = Context.Interaction.Message
            ?? throw new InvalidOperationException("Session button interaction must include a message.");

        return new DiscordSessionInteractionInput(
            SessionId: sessionId,
            InteractionId: Context.Interaction.Id.ToString(System.Globalization.CultureInfo.InvariantCulture),
            GuildId: guildId,
            ChannelId: Context.Channel.Id.ToString(CultureInfo.InvariantCulture),
            MessageId: message.Id.ToString(CultureInfo.InvariantCulture),
            UserId: Context.User.Id,
            Username: Context.User.Username,
            DisplayName: Context.User.GlobalName);
    }

    private async Task CompleteWithStoredReplyAsync(string interactionId)
    {
        var reply = interactionReplies.Take(interactionId);
        await CompleteResponseAsync(reply?.Text ?? "Session updated.");
    }

    private async Task CompleteScheduleUpdateResponseAsync(string interactionId, SessionInteractionResult result)
    {
        var updatedView = result.UpdatedView;
        if (updatedView is not null && SourceMessageHasDeleteAction())
        {
            updatedView = DiscordListSessionsHandler.AddManagerActions(updatedView);
        }

        if (updatedView is not null)
        {
            var (embeds, actionRows) = DiscordSessionBatchRenderer.Render(updatedView);
            await ModifyResponseAsync(options =>
            {
                options.Embeds = embeds;
                options.Components = actionRows;
            });
        }

        var reply = interactionReplies.Take(interactionId);
        await FollowupEphemeralAsync(reply?.Text ?? result.ReplyText);
    }

    private async Task CompleteDeleteResponseAsync(DiscordDeleteSessionResult result)
    {
        if (result.UpdatedView is not null)
        {
            var (embeds, actionRows) = DiscordSessionBatchRenderer.Render(result.UpdatedView);
            await ModifyResponseAsync(options =>
            {
                options.Embeds = embeds;
                options.Components = actionRows;
            });
        }
        else if (result.EmptyMessage is not null)
        {
            await ModifyResponseAsync(options =>
            {
                options.Content = result.EmptyMessage;
                options.Embeds = [];
                options.Components = [];
            });
        }

        await FollowupEphemeralAsync(result.ReplyText);
    }

    private Task CompleteResponseAsync(string text) =>
        ModifyResponseAsync(options => options.Content = text);

    private Task FollowupEphemeralAsync(string text) =>
        FollowupAsync(new InteractionMessageProperties()
            .WithContent(text)
            .WithFlags(MessageFlags.Ephemeral));

    private bool SourceMessageHasDeleteAction() =>
        Context.Interaction.Message?.Components.Any(ComponentContainsDeleteAction) == true;

    private static bool ComponentContainsDeleteAction(object? component)
    {
        if (component is null)
            return false;

        if (component is IInteractiveComponent interactive
            && interactive.CustomId.StartsWith("delete_session:", StringComparison.Ordinal))
            return true;

        var nestedComponents = component.GetType().GetProperty("Components")?.GetValue(component) as IEnumerable;
        if (nestedComponents is null)
            return false;

        foreach (var nestedComponent in nestedComponents)
        {
            if (ComponentContainsDeleteAction(nestedComponent))
                return true;
        }

        return false;
    }

    private static InteractionCallbackProperties CreateEphemeralReply(string text) =>
        InteractionCallback.Message(
            new InteractionMessageProperties()
                .WithContent(text)
                .WithFlags(MessageFlags.Ephemeral));
}
