using System.Globalization;
using GmRelay.Bot.Features.Sessions.RescheduleSession;
using GmRelay.Shared.Domain;
using GmRelay.Shared.Platform;
using Telegram.Bot;
using Telegram.Bot.Types;
using Telegram.Bot.Types.Enums;
using Telegram.Bot.Types.ReplyMarkups;

namespace GmRelay.Bot.Infrastructure.Telegram;

public sealed class TelegramPlatformMessenger(
    ITelegramBotClient bot,
    ILogger<TelegramPlatformMessenger> logger) : IPlatformMessenger
{
    public async Task<PlatformMessageRef> SendScheduleAsync(PlatformScheduleMessage message, CancellationToken ct)
    {
        EnsureTelegram(message.Group.Platform);

        var chatId = ParseLong(message.Group.ExternalGroupId);
        var threadId = ParseNullableInt(message.Group.ExternalThreadId);
        var renderResult = TelegramSessionBatchRenderer.Render(message.View);
        Message sentMessage;

        if (!string.IsNullOrWhiteSpace(message.ImageReference) && renderResult.Text.Length <= 1024)
        {
            try
            {
                sentMessage = await bot.SendPhoto(
                    chatId: chatId,
                    messageThreadId: threadId,
                    photo: InputFile.FromString(message.ImageReference),
                    caption: renderResult.Text,
                    parseMode: ParseMode.Html,
                    replyMarkup: renderResult.Markup,
                    cancellationToken: ct);
            }
            catch (Exception ex)
            {
                logger.LogWarning(ex, "Failed to send Telegram schedule image for group {ExternalGroupId}", message.Group.ExternalGroupId);
                sentMessage = await SendScheduleTextMessage(chatId, threadId, renderResult.Text, renderResult.Markup, ct);
            }
        }
        else
        {
            if (!string.IsNullOrWhiteSpace(message.ImageReference))
            {
                await TrySendScheduleImageOnly(chatId, threadId, message.View.Title, message.ImageReference, ct);
            }

            sentMessage = await SendScheduleTextMessage(chatId, threadId, renderResult.Text, renderResult.Markup, ct);
        }

        return new PlatformMessageRef(
            PlatformKind.Telegram,
            message.Group.ExternalGroupId,
            message.Group.ExternalThreadId,
            sentMessage.MessageId.ToString(CultureInfo.InvariantCulture));
    }

    public async Task UpdateScheduleAsync(PlatformScheduleMessage message, CancellationToken ct)
    {
        EnsureTelegram(message.Group.Platform);
        var existingMessage = message.ExistingMessage;
        if (existingMessage is null)
        {
            throw new ArgumentException("Existing schedule message reference is required.", nameof(message));
        }

        EnsureTelegram(existingMessage.Platform);
        if (!string.Equals(message.Group.ExternalGroupId, existingMessage.ExternalGroupId, StringComparison.Ordinal) ||
            !string.Equals(message.Group.ExternalThreadId, existingMessage.ExternalThreadId, StringComparison.Ordinal))
        {
            throw new ArgumentException("Existing schedule message reference must match the schedule group.", nameof(message));
        }

        var renderResult = TelegramSessionBatchRenderer.Render(message.View);
        await BatchMessageEditor.EditBatchMessageAsync(
            bot,
            chatId: ParseLong(existingMessage.ExternalGroupId),
            messageId: ParseInt(existingMessage.ExternalMessageId),
            text: renderResult.Text,
            replyMarkup: renderResult.Markup,
            ct);
    }

    public Task SendGroupMessageAsync(PlatformGroup group, string htmlText, CancellationToken ct)
    {
        EnsureTelegram(group.Platform);
        return bot.SendMessage(
            chatId: ParseLong(group.ExternalGroupId),
            messageThreadId: ParseNullableInt(group.ExternalThreadId),
            text: htmlText,
            parseMode: ParseMode.Html,
            cancellationToken: ct);
    }

    public async Task SendGroupMessageAsync(PlatformGroup group, string htmlText, IReadOnlyList<PlatformMessageAction> actions, CancellationToken ct)
    {
        EnsureTelegram(group.Platform);
        await bot.SendMessage(
            chatId: ParseLong(group.ExternalGroupId),
            messageThreadId: ParseNullableInt(group.ExternalThreadId),
            text: htmlText,
            parseMode: ParseMode.Html,
            replyMarkup: BuildActionsMarkup(actions),
            cancellationToken: ct);
    }

    public async Task UpdateGroupMessageAsync(PlatformMessageRef messageRef, string htmlText, IReadOnlyList<PlatformMessageAction> actions, CancellationToken ct)
    {
        EnsureTelegram(messageRef.Platform);
        await bot.EditMessageText(
            chatId: ParseLong(messageRef.ExternalGroupId),
            messageId: ParseInt(messageRef.ExternalMessageId),
            text: htmlText,
            parseMode: ParseMode.Html,
            replyMarkup: BuildActionsMarkup(actions),
            cancellationToken: ct);
    }

    public async Task<PlatformMessageRef> CreateThreadAsync(PlatformGroup group, string title, CancellationToken ct)
    {
        EnsureTelegram(group.Platform);
        var topic = await bot.CreateForumTopic(
            chatId: ParseLong(group.ExternalGroupId),
            name: title,
            cancellationToken: ct);

        return new PlatformMessageRef(
            PlatformKind.Telegram,
            group.ExternalGroupId,
            topic.MessageThreadId.ToString(CultureInfo.InvariantCulture),
            string.Empty);
    }

    public Task DeleteThreadAsync(PlatformGroup group, CancellationToken ct)
    {
        EnsureTelegram(group.Platform);
        if (string.IsNullOrWhiteSpace(group.ExternalThreadId))
        {
            return Task.CompletedTask;
        }

        return bot.DeleteForumTopic(
            ParseLong(group.ExternalGroupId),
            ParseInt(group.ExternalThreadId),
            cancellationToken: ct);
    }

    public Task DeleteMessageAsync(PlatformMessageRef messageRef, CancellationToken ct)
    {
        EnsureTelegram(messageRef.Platform);
        return bot.DeleteMessage(
            ParseLong(messageRef.ExternalGroupId),
            ParseInt(messageRef.ExternalMessageId),
            cancellationToken: ct);
    }

    public Task SendPrivateMessageAsync(PlatformPrivateMessage message, CancellationToken ct)
    {
        EnsureTelegram(message.Recipient.Platform);
        return bot.SendMessage(
            chatId: ParseLong(message.Recipient.ExternalUserId),
            text: message.HtmlText,
            parseMode: ParseMode.Html,
            cancellationToken: ct);
    }

    public Task AnswerInteractionAsync(PlatformInteractionReply reply, CancellationToken ct) =>
        bot.AnswerCallbackQuery(
            callbackQueryId: reply.InteractionId,
            text: reply.Text,
            showAlert: reply.ShowAlert,
            cancellationToken: ct);

    public async Task SendCalendarFileAsync(PlatformCalendarFile file, CancellationToken ct)
    {
        EnsureTelegram(file.Group.Platform);

        using var stream = new MemoryStream(file.Content);
        await bot.SendDocument(
            chatId: ParseLong(file.Group.ExternalGroupId),
            messageThreadId: ParseNullableInt(file.Group.ExternalThreadId),
            document: InputFile.FromStream(stream, file.FileName),
            caption: file.CaptionHtml,
            parseMode: ParseMode.Html,
            replyMarkup: BuildActionsMarkup(file.Actions),
            cancellationToken: ct);
    }

    public async Task<PlatformMessageRef> SendConfirmationRequestAsync(PlatformConfirmationRequest request, CancellationToken ct)
    {
        EnsureTelegram(request.Group.Platform);

        var chatId = ParseLong(request.Group.ExternalGroupId);
        var threadId = ParseNullableInt(request.Group.ExternalThreadId);
        var message = await bot.SendMessage(
            chatId: chatId,
            messageThreadId: threadId,
            text: BuildConfirmationText(request),
            parseMode: ParseMode.Html,
            replyMarkup: BuildRsvpKeyboard(request.SessionId),
            cancellationToken: ct);

        return TelegramPlatformIds.Message(chatId, threadId, message.MessageId);
    }

    public async Task UpdateConfirmationRequestAsync(PlatformRsvpMessageUpdate update, CancellationToken ct)
    {
        var request = update.Request;
        EnsureTelegram(request.Group.Platform);
        var existingMessage = request.ExistingMessage
            ?? throw new ArgumentException("Existing confirmation message reference is required.", nameof(update));

        EnsureTelegram(existingMessage.Platform);
        await bot.EditMessageText(
            chatId: ParseLong(existingMessage.ExternalGroupId),
            messageId: ParseInt(existingMessage.ExternalMessageId),
            text: BuildConfirmationText(request),
            parseMode: ParseMode.Html,
            replyMarkup: update.DisableActions ? null : BuildRsvpKeyboard(request.SessionId),
            cancellationToken: ct);
    }

    public async Task<PlatformMessageRef> SendJoinLinkNotificationAsync(
        PlatformJoinLinkNotification notification,
        CancellationToken ct)
    {
        EnsureTelegram(notification.Group.Platform);

        var chatId = ParseLong(notification.Group.ExternalGroupId);
        var threadId = ParseNullableInt(notification.Group.ExternalThreadId);
        var message = await bot.SendMessage(
            chatId: chatId,
            messageThreadId: threadId,
            text: BuildJoinLinkText(notification),
            cancellationToken: ct);

        return TelegramPlatformIds.Message(chatId, threadId, message.MessageId);
    }

    public Task SendDirectSessionNotificationAsync(
        PlatformDirectSessionNotification notification,
        CancellationToken ct)
    {
        EnsureTelegram(notification.Recipient.Platform);
        return bot.SendMessage(
            chatId: ParseLong(notification.Recipient.ExternalUserId),
            text: BuildDirectNotificationText(notification),
            parseMode: ParseMode.Html,
            cancellationToken: ct);
    }

    public async Task SendRsvpOutcomeAsync(PlatformRsvpOutcomeNotification notification, CancellationToken ct)
    {
        switch (notification.Kind)
        {
            case PlatformRsvpOutcomeKind.GroupAllConfirmed:
                if (notification.Group is null)
                {
                    throw new ArgumentException("Group notification requires a group.", nameof(notification));
                }

                EnsureTelegram(notification.Group.Platform);
                await bot.SendMessage(
                    chatId: ParseLong(notification.Group.ExternalGroupId),
                    messageThreadId: ParseNullableInt(notification.Group.ExternalThreadId),
                    text: $"🎉 Игра «{notification.Title}» подтверждена! Все участники на месте.",
                    cancellationToken: ct);
                break;

            case PlatformRsvpOutcomeKind.GmAllConfirmed:
            case PlatformRsvpOutcomeKind.GmPlayerDeclined:
                foreach (var recipient in notification.Recipients)
                {
                    EnsureTelegram(recipient.Platform);
                    await bot.SendMessage(
                        chatId: ParseLong(recipient.ExternalUserId),
                        text: BuildRsvpOutcomeDirectText(notification),
                        parseMode: ParseMode.Html,
                        cancellationToken: ct);
                }

                break;

            default:
                throw new ArgumentOutOfRangeException(nameof(notification), notification.Kind, "Unknown RSVP outcome kind.");
        }
    }

    public Task UpdateRescheduleVoteAsync(PlatformRescheduleVoteUpdate update, CancellationToken ct)
    {
        EnsureTelegram(update.Group.Platform);
        EnsureTelegram(update.ExistingMessage.Platform);

        var resultText = update.SelectedOption is not null
            ? $"✅ <b>Голосование завершено.</b>\nПобедил вариант {update.SelectedOption.DisplayOrder}: <b>{update.SelectedOption.ProposedAt.FormatMoscow()}</b> (МСК)."
            : $"❌ <b>Голосование завершено.</b>\n{System.Net.WebUtility.HtmlEncode(update.Decision.Reason)}";

        var text = $"""
            {HandleRescheduleTimeInputHandler.BuildVotingMessage(
                update.Title,
                update.CurrentScheduledAt,
                update.VotingDeadlineAt,
                update.Options,
                update.Participants,
                update.Votes)}

            {resultText}
            """;

        return bot.EditMessageText(
            chatId: ParseLong(update.ExistingMessage.ExternalGroupId),
            messageId: ParseInt(update.ExistingMessage.ExternalMessageId),
            text: text,
            parseMode: ParseMode.Html,
            cancellationToken: ct);
    }

    private async Task<Message> SendScheduleTextMessage(
        long chatId,
        int? threadId,
        string text,
        InlineKeyboardMarkup markup,
        CancellationToken ct) =>
        await bot.SendMessage(
            chatId: chatId,
            messageThreadId: threadId,
            text: text,
            parseMode: ParseMode.Html,
            replyMarkup: markup,
            cancellationToken: ct);

    private static string BuildConfirmationText(PlatformConfirmationRequest request)
    {
        var confirmed = request.Participants.Where(p => p.RsvpStatus == RsvpStatus.Confirmed).ToList();
        var declined = request.Participants.Where(p => p.RsvpStatus == RsvpStatus.Declined).ToList();
        var pending = request.Participants.Where(p => p.RsvpStatus == RsvpStatus.Pending).ToList();

        var lines = new List<string>
        {
            $"🎲 Подтвердите участие в «{System.Net.WebUtility.HtmlEncode(request.Title)}»",
            $"📅 {request.ScheduledAt.FormatMoscow()} (МСК)",
            string.Empty
        };

        foreach (var participant in confirmed)
        {
            lines.Add($"   ✅ {FormatTelegramParticipant(participant)}");
        }

        foreach (var participant in declined)
        {
            lines.Add($"   ❌ <s>{FormatTelegramParticipant(participant)}</s>");
        }

        foreach (var participant in pending)
        {
            lines.Add($"   ⏳ {FormatTelegramParticipant(participant)}");
        }

        lines.Add(string.Empty);

        if (request.Participants.Count > 0 && confirmed.Count == request.Participants.Count)
        {
            lines.Add($"Статус: ✅ все подтвердили ({confirmed.Count}/{request.Participants.Count})");
        }
        else if (declined.Count > 0)
        {
            lines.Add($"Статус: ⚠️ есть отказы ({confirmed.Count}/{request.Participants.Count} подтвердили)");
        }
        else
        {
            lines.Add($"Статус: ожидаем подтверждения ({confirmed.Count}/{request.Participants.Count})");
        }

        return string.Join("\n", lines);
    }

    private static string BuildJoinLinkText(PlatformJoinLinkNotification notification)
    {
        var mentions = string.Join(", ", notification.ConfirmedPlayers.Select(FormatTelegramParticipant));

        return $"""
            🎮 Игра «{notification.Title}» начинается через 5 минут!

            🔗 Ссылка на подключение:
            {notification.JoinLink}

            Участники: {mentions}

            Хорошей игры! 🎲
            """;
    }

    private static string BuildDirectNotificationText(PlatformDirectSessionNotification notification) =>
        notification.Kind switch
        {
            PlatformDirectSessionNotificationKind.ConfirmationRequest => $"""
                🎲 <b>Подтвердите участие в игре</b>

                📌 <b>{System.Net.WebUtility.HtmlEncode(notification.Title)}</b>
                📅 {notification.ScheduledAt.FormatMoscow()} (МСК)

                Ответьте кнопкой в групповом сообщении расписания.
                """,
            PlatformDirectSessionNotificationKind.OneHourReminder => $"""
                ⏰ <b>Игра начнётся примерно через 1 час</b>

                📌 <b>{System.Net.WebUtility.HtmlEncode(notification.Title)}</b>
                📅 {notification.ScheduledAt.FormatMoscow()} (МСК)
                🔗 {System.Net.WebUtility.HtmlEncode(notification.JoinLink ?? string.Empty)}
                """,
            PlatformDirectSessionNotificationKind.JoinLink => $"""
                🎮 <b>Игра начинается через 5 минут</b>

                📌 <b>{System.Net.WebUtility.HtmlEncode(notification.Title)}</b>
                🔗 {System.Net.WebUtility.HtmlEncode(notification.JoinLink ?? string.Empty)}
                """,
            PlatformDirectSessionNotificationKind.RescheduleApproved => $"""
                ✅ <b>Сессия перенесена по итогам голосования</b>

                📌 <b>{System.Net.WebUtility.HtmlEncode(notification.Title)}</b>
                📅 Новое время: <b>{notification.ScheduledAt.FormatMoscow()}</b> (МСК)
                """,
            PlatformDirectSessionNotificationKind.RescheduleRejected => $"""
                ❌ <b>Перенос сессии отклонён по итогам голосования</b>

                📌 <b>{System.Net.WebUtility.HtmlEncode(notification.Title)}</b>
                📅 Время остаётся прежним: <b>{notification.ScheduledAt.FormatMoscow()}</b> (МСК)
                Причина: {System.Net.WebUtility.HtmlEncode(notification.Reason ?? string.Empty)}
                """,
            _ => BuildFallbackDirectText(notification)
        };

    private static string BuildFallbackDirectText(PlatformDirectSessionNotification notification) =>
        $"<b>{System.Net.WebUtility.HtmlEncode(notification.Title)}</b>\n{notification.ScheduledAt.FormatMoscow()} (МСК)";

    private static string BuildRsvpOutcomeDirectText(PlatformRsvpOutcomeNotification notification) =>
        notification.Kind switch
        {
            PlatformRsvpOutcomeKind.GmAllConfirmed =>
                $"✅ Все подтвердили участие в «{System.Net.WebUtility.HtmlEncode(notification.Title)}» ({notification.ScheduledAt.FormatMoscow()} МСК).",
            PlatformRsvpOutcomeKind.GmPlayerDeclined =>
                $"🚨 Отмена! {System.Net.WebUtility.HtmlEncode(notification.ActorDisplayName ?? "Игрок")} не сможет прийти на игру «{System.Net.WebUtility.HtmlEncode(notification.Title)}».",
            _ => System.Net.WebUtility.HtmlEncode(notification.Title)
        };

    private static InlineKeyboardMarkup BuildRsvpKeyboard(Guid sessionId) =>
        new([
            [
                InlineKeyboardButton.WithCallbackData("✅ Буду", $"rsvp:confirm:{sessionId}"),
                InlineKeyboardButton.WithCallbackData("❌ Не смогу", $"rsvp:decline:{sessionId}")
            ]
        ]);

    private static string FormatTelegramParticipant(PlatformSessionParticipant participant) =>
        participant.User.ExternalUsername is not null
            ? $"@{participant.User.ExternalUsername}"
            : System.Net.WebUtility.HtmlEncode(participant.User.DisplayName);

    private async Task TrySendScheduleImageOnly(
        long chatId,
        int? threadId,
        string title,
        string imageReference,
        CancellationToken ct)
    {
        try
        {
            await bot.SendPhoto(
                chatId: chatId,
                messageThreadId: threadId,
                photo: InputFile.FromString(imageReference),
                caption: $"🎲 {System.Net.WebUtility.HtmlEncode(title)}",
                parseMode: ParseMode.Html,
                cancellationToken: ct);
        }
        catch (Exception ex)
        {
            logger.LogWarning(ex, "Failed to send Telegram schedule image for chat {ChatId}", chatId);
        }
    }

    private static InlineKeyboardMarkup? BuildActionsMarkup(IReadOnlyList<PlatformMessageAction> actions)
    {
        if (actions.Count == 0)
        {
            return null;
        }

        return new InlineKeyboardMarkup(
            actions.Select(action => new[]
            {
                Uri.TryCreate(action.Payload, UriKind.Absolute, out var uri) &&
                (uri.Scheme == Uri.UriSchemeHttp || uri.Scheme == Uri.UriSchemeHttps)
                    ? InlineKeyboardButton.WithUrl(action.Label, action.Payload)
                    : InlineKeyboardButton.WithCallbackData(action.Label, action.Payload)
            }));
    }

    private static void EnsureTelegram(PlatformKind platform)
    {
        if (platform != PlatformKind.Telegram)
        {
            throw new NotSupportedException($"Telegram messenger cannot send messages for platform {platform}.");
        }
    }

    private static long ParseLong(string value) => long.Parse(value, CultureInfo.InvariantCulture);

    private static int ParseInt(string value) => int.Parse(value, CultureInfo.InvariantCulture);

    private static int? ParseNullableInt(string? value) =>
        string.IsNullOrWhiteSpace(value) ? null : int.Parse(value, CultureInfo.InvariantCulture);
}
