using Dapper;
using GmRelay.Bot.Features.Notifications;
using GmRelay.Shared.Domain;
using GmRelay.Shared.Features.Sessions.RescheduleSession;
using GmRelay.Shared.Platform;
using GmRelay.Shared.Rendering;
using Npgsql;
using Telegram.Bot;
using Telegram.Bot.Types;
using Telegram.Bot.Types.ReplyMarkups;
using GmRelay.Bot.Infrastructure.Telegram;

namespace GmRelay.Bot.Features.Sessions.RescheduleSession;

/// <summary>
/// Telegram adapter for reschedule time input.
/// Delegates core logic to the shared handler, then performs Telegram-specific
/// message sending, DM notifications, vote_message_id storage, and cleanup.
/// </summary>
public sealed class HandleRescheduleTimeInputHandler(
    GmRelay.Shared.Features.Sessions.RescheduleSession.HandleRescheduleTimeInputHandler sharedHandler,
    NpgsqlDataSource dataSource,
    ITelegramBotClient bot,
    IPlatformMessenger messenger,
    DirectSessionNotificationSender directSender,
    ILogger<HandleRescheduleTimeInputHandler> logger)
{
    public async Task<bool> TryHandleAsync(Message message, CancellationToken ct)
    {
        if (message.From is null || string.IsNullOrWhiteSpace(message.Text))
            return false;

        var command = new GmRelay.Shared.Features.Sessions.RescheduleSession.HandleRescheduleTimeInputCommand(
            new PlatformUser(
                PlatformKind.Telegram,
                message.From.Id.ToString(),
                message.From.FirstName + (string.IsNullOrEmpty(message.From.LastName) ? "" : $" {message.From.LastName}"),
                message.From.Username),
            TelegramPlatformIds.Group(message.Chat.Id, message.MessageThreadId, message.Chat.Title),
            message.Text.Trim());

        var result = await sharedHandler.HandleAsync(command, ct);
        if (!result.Handled)
            return false;

        if (!string.IsNullOrEmpty(result.ReplyText) && !result.IsRescheduledImmediately)
        {
            await messenger.SendGroupMessageAsync(
                command.Group,
                $"""⚠️ {result.ReplyText}\n\nИспользуйте формат:\n<code>25.04.2026 19:30\n26.04.2026 18:00\nДедлайн: 25.04.2026 12:00</code>""",
                ct);
            return true;
        }

        if (result.IsRescheduledImmediately)
        {
            if (result.UpdatedView is not null && result.BatchMessageId.HasValue)
            {
                await TryUpdateBatchMessage(
                    command.Group,
                    result.UpdatedView,
                    TelegramPlatformIds.Message(message.Chat.Id, message.MessageThreadId, result.BatchMessageId.Value),
                    ct);
            }

            await messenger.SendGroupMessageAsync(command.Group, result.ReplyText!, ct);
            await TryDeleteMessage(message.Chat.Id, message.MessageId, ct);
            return true;
        }

        // Voting mode
        var voteText = BuildVotingMessage(
            result.Title!,
            result.CurrentScheduledAt,
            result.VotingDeadlineAt!.Value,
            result.Options,
            result.Participants,
            []);

        var keyboard = BuildVotingKeyboard(result.Options);

        var voteMsg = await bot.SendMessage(
            chatId: message.Chat.Id,
            messageThreadId: message.MessageThreadId,
            text: voteText,
            parseMode: Telegram.Bot.Types.Enums.ParseMode.Html,
            replyMarkup: keyboard,
            cancellationToken: ct);

        var mode = await GetNotificationModeAsync(result.ProposalId!.Value, ct);
        if (mode.ShouldSendDirectMessages())
        {
            var optionsText = string.Join(
                "\n",
                result.Options.Select(option => $"{option.DisplayOrder}. <b>{option.ProposedAt.FormatMoscow()}</b> (МСК)"));
            var directText = $"""
                🔄 <b>Голосование за перенос сессии</b>

                📌 <b>{System.Net.WebUtility.HtmlEncode(result.Title)}</b>
                📅 Текущее время: <b>{result.CurrentScheduledAt.FormatMoscow()}</b> (МСК)
                🗳 Варианты:
                {optionsText}

                ⏳ Дедлайн: <b>{result.VotingDeadlineAt.Value.FormatMoscow()}</b> (МСК)

                Проголосуйте кнопкой в групповом сообщении.
                """;

            await directSender.SendAsync(
                result.Participants.Select(p => new DirectNotificationRecipient(
                    p.TelegramId,
                    p.DisplayName)),
                directText,
                "reschedule-vote",
                result.ProposalId.Value,
                ct);
        }

        await using var connection = await dataSource.OpenConnectionAsync(ct);
        await connection.ExecuteAsync(
            "UPDATE reschedule_proposals SET vote_message_id = @MsgId WHERE id = @Id",
            new { MsgId = voteMsg.MessageId, Id = result.ProposalId.Value });

        logger.LogInformation(
            "Reschedule voting started for session {SessionId}, proposal {ProposalId}, options {OptionCount}, deadline {Deadline}",
            result.ProposalId.Value,
            result.ProposalId.Value,
            result.Options.Count,
            result.VotingDeadlineAt.Value);

        await TryDeleteMessage(message.Chat.Id, message.MessageId, ct);
        return true;
    }

    private async Task<SessionNotificationMode> GetNotificationModeAsync(Guid proposalId, CancellationToken ct)
    {
        await using var connection = await dataSource.OpenConnectionAsync(ct);
        var raw = await connection.QuerySingleOrDefaultAsync<string?>(
            """
            SELECT s.notification_mode
            FROM sessions s
            JOIN reschedule_proposals rp ON rp.session_id = s.id
            WHERE rp.id = @Id
            """,
            new { Id = proposalId });
        return SessionNotificationModeExtensions.FromDatabaseValue(raw ?? string.Empty);
    }

    private async Task TryUpdateBatchMessage(
        PlatformGroup group,
        SessionBatchViewModel view,
        PlatformMessageRef scheduleMessage,
        CancellationToken ct)
    {
        try
        {
            await messenger.UpdateScheduleAsync(
                new PlatformScheduleMessage(group, view, scheduleMessage),
                ct);
        }
        catch (Exception ex)
        {
            logger.LogWarning(ex, "Failed to update batch message after immediate reschedule");
        }
    }

    internal static string BuildVotingMessage(
        string title,
        DateTime currentTime,
        DateTimeOffset deadline,
        IReadOnlyList<RescheduleOptionDto> options,
        IReadOnlyList<VoteParticipantDto> participants,
        IReadOnlyList<RescheduleOptionVoteDto> votes)
    {
        var votesByOption = votes
            .GroupBy(v => v.OptionId)
            .ToDictionary(g => g.Key, g => g.ToList());
        var votedPlayerIds = votes.Select(v => v.PlayerId).ToHashSet();
        var pendingParticipants = participants
            .Where(p => !votedPlayerIds.Contains(p.PlayerId))
            .Select(FormatParticipantName)
            .ToList();

        var lines = new List<string>
        {
            $"""🔄 <b>Перенос сессии «{System.Net.WebUtility.HtmlEncode(title)}»</b>""",
            "",
            $"📅 Текущее время: <b>{currentTime.FormatMoscow()}</b> (МСК)",
            $"⏳ Дедлайн: <b>{deadline.FormatMoscow()}</b> (МСК)",
            "",
            "Выберите один из вариантов:"
        };

        foreach (var option in options.OrderBy(x => x.DisplayOrder))
        {
            var optionVotes = votesByOption.GetValueOrDefault(option.OptionId, []);
            lines.Add(
                $"{option.DisplayOrder}. <b>{option.ProposedAt.FormatMoscow()}</b> (МСК) — {FormatVoteCount(optionVotes.Count)}");

            if (optionVotes.Count > 0)
            {
                lines.Add($"   {string.Join(", ", optionVotes.Select(FormatParticipantName))}");
            }
        }

        if (pendingParticipants.Count > 0)
        {
            lines.Add("");
            lines.Add($"Не проголосовали: {string.Join(", ", pendingParticipants)}");
        }

        lines.Add("");
        lines.Add($"Голосов: {votedPlayerIds.Count}/{participants.Count}");
        lines.Add("Правило: побеждает вариант с большинством голосов к дедлайну; при ничьей перенос не применяется.");

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

    internal static InlineKeyboardMarkup BuildVotingKeyboard(IReadOnlyList<RescheduleOptionDto> options)
    {
        return new InlineKeyboardMarkup(
            options
                .OrderBy(option => option.DisplayOrder)
                .Select(option => new[]
                {
                    InlineKeyboardButton.WithCallbackData(
                        $"{option.DisplayOrder}. {FormatButtonTime(option.ProposedAt)}",
                        $"reschedule_vote:{option.OptionId}")
                }));
    }

    internal static string FormatParticipantName(VoteParticipantDto participant)
    {
        return participant.TelegramUsername is { Length: > 0 } username
            ? $"@{System.Net.WebUtility.HtmlEncode(username)}"
            : System.Net.WebUtility.HtmlEncode(participant.DisplayName);
    }

    internal static string FormatParticipantName(RescheduleOptionVoteDto vote)
    {
        return vote.TelegramUsername is { Length: > 0 } username
            ? $"@{System.Net.WebUtility.HtmlEncode(username)}"
            : System.Net.WebUtility.HtmlEncode(vote.DisplayName);
    }

    private static string FormatVoteCount(int count)
    {
        var modulo100 = count % 100;
        var modulo10 = count % 10;
        var word = modulo100 is >= 11 and <= 14
            ? "голосов"
            : modulo10 switch
            {
                1 => "голос",
                >= 2 and <= 4 => "голоса",
                _ => "голосов"
            };

        return $"{count} {word}";
    }

    private static string FormatButtonTime(DateTimeOffset utc)
        => utc.ToOffset(TimeSpan.FromHours(3)).ToString(
            "dd.MM HH:mm",
            System.Globalization.CultureInfo.InvariantCulture);

    private async Task TryDeleteMessage(long chatId, int messageId, CancellationToken ct)
    {
        try
        {
            await bot.DeleteMessage(chatId, messageId, cancellationToken: ct);
        }
        catch (Exception ex)
        {
            logger.LogWarning(ex, "Failed to delete message {MessageId} in chat {ChatId}", messageId, chatId);
        }
    }
}
