using System; using System.Linq; using System.Threading.Tasks; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Logging; using QRCoder; using Telegram.Bot; using Telegram.Bot.Exceptions; using Telegram.Bot.Types; using Telegram.Bot.Types.ReplyMarkups; using TeleBot.Models; using TeleBot.Services; using TeleBot.UI; using LittleShop.Client.Models; namespace TeleBot.Handlers { public interface ICallbackHandler { Task HandleCallbackAsync(ITelegramBotClient bot, CallbackQuery callbackQuery); } public class CallbackHandler : ICallbackHandler { private readonly ISessionManager _sessionManager; private readonly ILittleShopService _shopService; private readonly IPrivacyService _privacyService; private readonly IProductCarouselService _carouselService; private readonly IConfiguration _configuration; private readonly ILogger _logger; public CallbackHandler( ISessionManager sessionManager, ILittleShopService shopService, IPrivacyService privacyService, IProductCarouselService carouselService, IConfiguration configuration, ILogger logger) { _sessionManager = sessionManager; _shopService = shopService; _privacyService = privacyService; _carouselService = carouselService; _configuration = configuration; _logger = logger; } public async Task HandleCallbackAsync(ITelegramBotClient bot, CallbackQuery callbackQuery) { if (callbackQuery.Message == null || callbackQuery.Data == null) return; var session = await _sessionManager.GetOrCreateSessionAsync(callbackQuery.From.Id); bool callbackAnswered = false; try { // Answer callback immediately to prevent timeout await bot.AnswerCallbackQueryAsync(callbackQuery.Id); callbackAnswered = true; var data = callbackQuery.Data.Split(':'); var action = data[0]; switch (action) { case "menu": await HandleMainMenu(bot, callbackQuery.Message, session); break; case "browse": await HandleBrowse(bot, callbackQuery.Message, session); break; case "category": await HandleCategory(bot, callbackQuery.Message, session, Guid.Parse(data[1])); break; case "products": if (data.Length > 1 && data[1] == "page") { await HandleProductsPage(bot, callbackQuery.Message, session, data); } else { await HandleProductList(bot, callbackQuery.Message, session, data); } break; case "product": await HandleProductDetail(bot, callbackQuery.Message, session, Guid.Parse(data[1])); break; case "qty": await HandleQuantityChange(bot, callbackQuery.Message, session, data); break; case "add": await HandleAddToCart(bot, callbackQuery, session, data); break; case "quickbuy": await HandleQuickBuy(bot, callbackQuery, session, data); break; case "quickbuyvar": await HandleQuickBuyWithVariation(bot, callbackQuery, session, data); break; case "cart": await HandleViewCart(bot, callbackQuery.Message, session); break; case "remove": await HandleRemoveFromCart(bot, callbackQuery, session, Guid.Parse(data[1])); break; case "clear_cart": await HandleClearCart(bot, callbackQuery, session); break; case "checkout": await HandleCheckout(bot, callbackQuery.Message, session); break; case "confirm_order": await HandleConfirmOrder(bot, callbackQuery.Message, session, callbackQuery.From); break; case "pay": await HandlePayment(bot, callbackQuery.Message, session, data[1]); break; case "orders": await HandleViewOrders(bot, callbackQuery.Message, session, callbackQuery.From); break; case "order": await HandleViewOrder(bot, callbackQuery.Message, session, Guid.Parse(data[1]), callbackQuery.From); break; case "privacy": await HandlePrivacySettings(bot, callbackQuery.Message, session, data.Length > 1 ? data[1] : null); break; case "help": await HandleHelp(bot, callbackQuery.Message); break; case "support": await HandleSupportCallback(bot, callbackQuery, session); break; case "refresh_conversation": await HandleRefreshConversation(bot, callbackQuery, session); break; case "exit_chat": await HandleExitChat(bot, callbackQuery, session); break; case "noop": // No operation - used for display-only buttons break; case "cancel_support": await HandleCancelSupport(bot, callbackQuery, session); break; default: _logger.LogWarning("Unknown callback action: {Action}", action); break; } await _sessionManager.UpdateSessionAsync(session); } catch (Exception ex) { _logger.LogError(ex, "Error handling callback {Data}", callbackQuery.Data); // Only try to answer callback if not already answered if (!callbackAnswered) { try { await bot.AnswerCallbackQueryAsync( callbackQuery.Id, "An error occurred. Please try again.", showAlert: true ); } catch (ApiRequestException apiEx) when (apiEx.Message.Contains("query is too old")) { // Callback already expired, ignore _logger.LogDebug("Callback query already expired"); } } } } private async Task HandleMainMenu(ITelegramBotClient bot, Message message, UserSession session) { await bot.EditMessageTextAsync( message.Chat.Id, message.MessageId, MessageFormatter.FormatWelcome(true), parseMode: Telegram.Bot.Types.Enums.ParseMode.Markdown, replyMarkup: MenuBuilder.MainMenu() ); session.State = SessionState.MainMenu; } private async Task HandleBrowse(ITelegramBotClient bot, Message message, UserSession session) { var categories = await _shopService.GetCategoriesAsync(); await bot.EditMessageTextAsync( message.Chat.Id, message.MessageId, MessageFormatter.FormatCategories(categories), parseMode: Telegram.Bot.Types.Enums.ParseMode.Markdown, replyMarkup: MenuBuilder.CategoryMenu(categories) ); session.State = SessionState.BrowsingCategories; } private async Task HandleCategory(ITelegramBotClient bot, Message message, UserSession session, Guid categoryId) { var categories = await _shopService.GetCategoriesAsync(); var category = categories.FirstOrDefault(c => c.Id == categoryId); var products = await _shopService.GetProductsAsync(categoryId, 1); // Edit the original message to show category header (bigger, more prominent) var headerText = $"**{category?.Name ?? "Unknown Category"} PRODUCTS**\n\nBrowse individual products below:"; await bot.EditMessageTextAsync( message.Chat.Id, message.MessageId, headerText, parseMode: Telegram.Bot.Types.Enums.ParseMode.Markdown, replyMarkup: MenuBuilder.CategoryNavigationMenu(categoryId) ); // Send individual product bubbles if (products.Items.Any()) { foreach (var product in products.Items) { await bot.SendTextMessageAsync( message.Chat.Id, MessageFormatter.FormatSingleProduct(product), parseMode: Telegram.Bot.Types.Enums.ParseMode.Markdown, replyMarkup: MenuBuilder.SingleProductMenu(product.Id) ); } // Send navigation buttons after all products await bot.SendTextMessageAsync( message.Chat.Id, ".", replyMarkup: MenuBuilder.ProductNavigationMenu(categoryId) ); } else { await bot.SendTextMessageAsync( message.Chat.Id, "No products available in this category.", replyMarkup: MenuBuilder.BackToCategoriesMenu() ); } session.State = SessionState.ViewingProducts; } private async Task HandleProductList(ITelegramBotClient bot, Message message, UserSession session, string[] data) { // Format: products:categoryId:page or products:all:page var page = int.Parse(data[2]); Guid? categoryId = data[1] != "all" ? Guid.Parse(data[1]) : null; var products = await _shopService.GetProductsAsync(categoryId, page); string? categoryName = null; if (categoryId.HasValue) { var categories = await _shopService.GetCategoriesAsync(); categoryName = categories.FirstOrDefault(c => c.Id == categoryId)?.Name; } // Use carousel service to send products with images await _carouselService.SendProductCarouselAsync(bot, message.Chat.Id, products, categoryName, page); session.State = SessionState.ViewingProducts; } private async Task HandleProductsPage(ITelegramBotClient bot, Message message, UserSession session, string[] data) { // Format: products:page:pageNumber var page = int.Parse(data[2]); // Get products for all categories (no specific category filter) var products = await _shopService.GetProductsAsync(null, page); // Use carousel service to send products with images await _carouselService.SendProductCarouselAsync(bot, message.Chat.Id, products, "All Categories", page); session.State = SessionState.ViewingProducts; } private async Task HandleProductDetail(ITelegramBotClient bot, Message message, UserSession session, Guid productId) { var product = await _shopService.GetProductAsync(productId); if (product == null) { await bot.AnswerCallbackQueryAsync("", "Product not found", showAlert: true); return; } // Store current product in temp data for quantity selection session.TempData["current_product"] = productId; session.TempData["current_quantity"] = 1; // Use carousel service to send product with image await _carouselService.SendSingleProductWithImageAsync(bot, message.Chat.Id, product); session.State = SessionState.ViewingProduct; } private async Task HandleQuantityChange(ITelegramBotClient bot, Message message, UserSession session, string[] data) { // Format: qty:productId:newQuantity var productId = Guid.Parse(data[1]); var quantity = int.Parse(data[2]); var product = await _shopService.GetProductAsync(productId); if (product == null) return; session.TempData["current_quantity"] = quantity; await bot.EditMessageReplyMarkupAsync( message.Chat.Id, message.MessageId, MenuBuilder.ProductDetailMenu(product, quantity) ); } private async Task HandleAddToCart(ITelegramBotClient bot, CallbackQuery callbackQuery, UserSession session, string[] data) { // Format: add:productId:quantity or add:productId:quantity:multiBuyId or add:productId:quantity:multiBuyId:variant var productId = Guid.Parse(data[1]); var quantity = int.Parse(data[2]); Guid? multiBuyId = data.Length > 3 && !data[3].Contains(":") ? Guid.Parse(data[3]) : null; string? selectedVariant = data.Length > 4 ? data[4] : (data.Length > 3 && data[3].Contains(":") ? data[3] : null); var product = await _shopService.GetProductAsync(productId); if (product == null) { await bot.AnswerCallbackQueryAsync(callbackQuery.Id, "Product not found", showAlert: true); return; } // If product has variants but none selected, show variant selection if (selectedVariant == null && product.Variants?.Any() == true) { await ShowVariantSelection(bot, callbackQuery.Message!, session, product, quantity, multiBuyId); return; } // Get price based on multi-buy or base product decimal price = product.Price; string itemName = product.Name; if (multiBuyId.HasValue && product.MultiBuys != null) { var multiBuy = product.MultiBuys.FirstOrDefault(mb => mb.Id == multiBuyId); if (multiBuy != null) { price = multiBuy.Price; itemName = $"{product.Name} ({multiBuy.Name})"; quantity = multiBuy.Quantity; // Use multi-buy quantity } } // Add variant to item name if selected if (!string.IsNullOrEmpty(selectedVariant)) { itemName += $" - {selectedVariant}"; } session.Cart.AddItem(productId, itemName, price, quantity, multiBuyId, selectedVariant); await bot.AnswerCallbackQueryAsync( callbackQuery.Id, $"✅ Added {quantity}x {itemName} to cart", showAlert: false ); // Send new cart message instead of editing await SendNewCartMessage(bot, callbackQuery.Message!.Chat.Id, session); } private async Task HandleViewCart(ITelegramBotClient bot, Message message, UserSession session) { await bot.EditMessageTextAsync( message.Chat.Id, message.MessageId, MessageFormatter.FormatCart(session.Cart), parseMode: Telegram.Bot.Types.Enums.ParseMode.Markdown, replyMarkup: MenuBuilder.CartMenu(session.Cart) ); session.State = SessionState.ViewingCart; } private async Task SendNewCartMessage(ITelegramBotClient bot, long chatId, UserSession session) { await bot.SendTextMessageAsync( chatId, MessageFormatter.FormatCart(session.Cart), parseMode: Telegram.Bot.Types.Enums.ParseMode.Markdown, replyMarkup: MenuBuilder.CartMenu(session.Cart) ); session.State = SessionState.ViewingCart; } private async Task ShowVariantSelection(ITelegramBotClient bot, Message message, UserSession session, Product product, int quantity, Guid? multiBuyId) { var text = $"**{product.Name}**\n\n"; text += "Please select a variant:\n"; var buttons = new List(); if (product.Variants?.Any() == true) { foreach (var variant in product.Variants.OrderBy(v => v.SortOrder)) { var callbackData = multiBuyId.HasValue ? $"add:{product.Id}:{quantity}:{multiBuyId}:{variant.Name}" : $"add:{product.Id}:{quantity}::{variant.Name}"; buttons.Add(new[] { InlineKeyboardButton.WithCallbackData(variant.Name, callbackData) }); } } buttons.Add(new[] { InlineKeyboardButton.WithCallbackData("« Back", $"product:{product.Id}") }); await bot.EditMessageTextAsync( message.Chat.Id, message.MessageId, text, parseMode: Telegram.Bot.Types.Enums.ParseMode.Markdown, replyMarkup: new InlineKeyboardMarkup(buttons) ); } private async Task HandleQuickBuy(ITelegramBotClient bot, CallbackQuery callbackQuery, UserSession session, string[] data) { // Format: quickbuy:productId:quantity var productId = Guid.Parse(data[1]); var quantity = int.Parse(data[2]); var product = await _shopService.GetProductAsync(productId); if (product == null) { await bot.AnswerCallbackQueryAsync(callbackQuery.Id, "Product not found", showAlert: true); return; } // If variants exist, show variant selection with quickbuy flow if (product.Variants?.Any() == true) { await ShowVariantSelectionForQuickBuy(bot, callbackQuery.Message!, session, product, quantity); return; } // Add to cart with base product session.Cart.AddItem(productId, product.Name, product.Price, quantity, null, null); await bot.AnswerCallbackQueryAsync( callbackQuery.Id, $"✅ Added {quantity}x {product.Name} to cart", showAlert: false ); // Send cart summary in new message await bot.SendTextMessageAsync( callbackQuery.Message!.Chat.Id, MessageFormatter.FormatCart(session.Cart), parseMode: Telegram.Bot.Types.Enums.ParseMode.Markdown, replyMarkup: MenuBuilder.CartMenu(session.Cart) ); // Immediately proceed to checkout await Task.Delay(500); // Small delay for better UX await HandleCheckout(bot, callbackQuery.Message, session); } private async Task ShowVariantSelectionForQuickBuy(ITelegramBotClient bot, Message message, UserSession session, Product product, int quantity) { var text = $"**Quick Buy: {product.Name}**\n\n"; text += "Please select a variant:\n"; var buttons = new List(); if (product.Variants?.Any() == true) { // Add buttons for each variant with quickbuy flow foreach (var variant in product.Variants.OrderBy(v => v.SortOrder)) { buttons.Add(new[] { InlineKeyboardButton.WithCallbackData(variant.Name, $"quickbuyvar:{product.Id}:{quantity}:{variant.Name}") }); } } // Add back button buttons.Add(new[] { InlineKeyboardButton.WithCallbackData("⬅️ Back", "menu") }); await bot.SendTextMessageAsync( message.Chat.Id, text + "\n\n*Select an option for quick checkout:*", parseMode: Telegram.Bot.Types.Enums.ParseMode.Markdown, replyMarkup: new InlineKeyboardMarkup(buttons) ); } private async Task HandleQuickBuyWithVariation(ITelegramBotClient bot, CallbackQuery callbackQuery, UserSession session, string[] data) { // Format: quickbuyvar:productId:quantity:variantName var productId = Guid.Parse(data[1]); var quantity = int.Parse(data[2]); var variantName = data[3]; var product = await _shopService.GetProductAsync(productId); if (product == null) { await bot.AnswerCallbackQueryAsync(callbackQuery.Id, "Product not found", showAlert: true); return; } // Add to cart with variant var itemName = $"{product.Name} - {variantName}"; session.Cart.AddItem(productId, itemName, product.Price, quantity, null, variantName); await bot.AnswerCallbackQueryAsync( callbackQuery.Id, $"✅ Added {quantity}x {itemName} to cart", showAlert: false ); // Send cart summary in new message await bot.SendTextMessageAsync( callbackQuery.Message!.Chat.Id, MessageFormatter.FormatCart(session.Cart), parseMode: Telegram.Bot.Types.Enums.ParseMode.Markdown, replyMarkup: MenuBuilder.CartMenu(session.Cart) ); // Immediately proceed to checkout await Task.Delay(500); // Small delay for better UX await HandleCheckout(bot, callbackQuery.Message, session); } private async Task HandleRemoveFromCart(ITelegramBotClient bot, CallbackQuery callbackQuery, UserSession session, Guid productId) { var item = session.Cart.Items.FirstOrDefault(i => i.ProductId == productId); if (item != null) { session.Cart.RemoveItem(productId); await bot.AnswerCallbackQueryAsync( callbackQuery.Id, $"❌ Removed {item.ProductName} from cart", showAlert: false ); } await HandleViewCart(bot, callbackQuery.Message!, session); } private async Task HandleClearCart(ITelegramBotClient bot, CallbackQuery callbackQuery, UserSession session) { session.Cart.Clear(); await bot.AnswerCallbackQueryAsync( callbackQuery.Id, "🗑️ Cart cleared", showAlert: false ); await HandleViewCart(bot, callbackQuery.Message!, session); } private async Task HandleCheckout(ITelegramBotClient bot, Message message, UserSession session) { if (session.Cart.IsEmpty()) { await bot.AnswerCallbackQueryAsync("", "Your cart is empty", showAlert: true); return; } // Initialize order flow session.OrderFlow = new OrderFlowData { UsePGPEncryption = session.Privacy.RequirePGP }; session.State = SessionState.CheckoutFlow; // Send new message for checkout instead of editing await bot.SendTextMessageAsync( message.Chat.Id, "📦 *Checkout - Step 1/5*\n\n" + "Please enter your shipping name:\n\n" + "_Reply to this message with your name_", parseMode: Telegram.Bot.Types.Enums.ParseMode.Markdown ); } private async Task HandleConfirmOrder(ITelegramBotClient bot, Message message, UserSession session, User telegramUser) { if (session.OrderFlow == null || session.Cart.IsEmpty()) { await bot.AnswerCallbackQueryAsync("", "Invalid order state", showAlert: true); return; } // Create the order with customer information var order = await _shopService.CreateOrderAsync( session, telegramUser.Id, telegramUser.Username ?? "", $"{telegramUser.FirstName} {telegramUser.LastName}".Trim(), telegramUser.FirstName ?? "", telegramUser.LastName ?? ""); if (order == null) { await bot.EditMessageTextAsync( message.Chat.Id, message.MessageId, "❌ Failed to create order. Please try again.", replyMarkup: MenuBuilder.CartMenu(session.Cart) ); return; } // Backup cart in case payment fails - we'll need to restore it var cartBackup = System.Text.Json.JsonSerializer.Serialize(session.Cart); session.TempData["cart_backup"] = cartBackup; // Clear cart after successful order (will restore if payment fails) session.Cart.Clear(); // Store order ID for payment session.TempData["current_order_id"] = order.Id; // Show payment options - get currencies dynamically from SilverPay support + admin settings var currencies = await _shopService.GetAvailableCurrenciesAsync(); await bot.EditMessageTextAsync( message.Chat.Id, message.MessageId, $"✅ *Order Created Successfully!*\n\n" + $"Order ID: `{order.Id}`\n" + $"Total: ${order.TotalAmount:F2}\n\n" + "Select your preferred payment method:", parseMode: Telegram.Bot.Types.Enums.ParseMode.Markdown, replyMarkup: MenuBuilder.PaymentMethodMenu(currencies) ); } private async Task HandlePayment(ITelegramBotClient bot, Message message, UserSession session, string currency) { if (!session.TempData.TryGetValue("current_order_id", out var orderIdObj) || orderIdObj is not Guid orderId) { await SafeEditMessageAsync( bot, message.Chat.Id, message.MessageId, "❌ Order not found. Please start a new order.", Telegram.Bot.Types.Enums.ParseMode.Markdown, MenuBuilder.MainMenu() ); return; } // Show processing message await SafeEditMessageAsync( bot, message.Chat.Id, message.MessageId, $"🔄 Creating {currency} payment...\n\nPlease wait...", Telegram.Bot.Types.Enums.ParseMode.Markdown ); try { var payment = await _shopService.CreatePaymentAsync(orderId, currency); if (payment == null) { // Restore cart from backup since payment failed if (session.TempData.TryGetValue("cart_backup", out var cartBackupObj) && cartBackupObj is string cartBackupJson) { try { session.Cart = System.Text.Json.JsonSerializer.Deserialize(cartBackupJson) ?? new ShoppingCart(); session.TempData.Remove("cart_backup"); } catch { // If restoration fails, cart remains empty } } await SafeEditMessageAsync( bot, message.Chat.Id, message.MessageId, $"❌ *Payment Creation Failed*\n\n" + $"Unable to create {currency} payment.\n" + $"This might be due to:\n" + $"• Payment gateway temporarily unavailable\n" + $"• Network connectivity issues\n\n" + $"Your cart has been restored. Please try again.", Telegram.Bot.Types.Enums.ParseMode.Markdown, MenuBuilder.CartMenu(session.Cart) ); return; } // Payment created successfully, remove cart backup session.TempData.Remove("cart_backup"); // Continue with display var paymentText = MessageFormatter.FormatPayment(payment); await DisplayPaymentInfo(bot, message, payment, paymentText); } catch (Exception ex) { _logger.LogError(ex, "Failed to create payment for order {OrderId} with currency {Currency}", orderId, currency); // Restore cart from backup since payment failed if (session.TempData.TryGetValue("cart_backup", out var cartBackupObj) && cartBackupObj is string cartBackupJson) { try { session.Cart = System.Text.Json.JsonSerializer.Deserialize(cartBackupJson) ?? new ShoppingCart(); session.TempData.Remove("cart_backup"); } catch { // If restoration fails, cart remains empty } } await SafeEditMessageAsync( bot, message.Chat.Id, message.MessageId, $"❌ *Payment System Error*\n\n" + $"Sorry, there was a technical issue creating your {currency} payment.\n\n" + $"Our payment system may be undergoing maintenance.\n" + $"Your cart has been restored. Please try again later.", Telegram.Bot.Types.Enums.ParseMode.Markdown, MenuBuilder.CartMenu(session.Cart) ); return; } } /// /// Safely edit a message only if the content has changed /// private async Task SafeEditMessageAsync(ITelegramBotClient bot, ChatId chatId, int messageId, string newText, Telegram.Bot.Types.Enums.ParseMode parseMode = Telegram.Bot.Types.Enums.ParseMode.Html, InlineKeyboardMarkup? replyMarkup = null) { try { await bot.EditMessageTextAsync(chatId, messageId, newText, parseMode: parseMode, replyMarkup: replyMarkup); } catch (ApiRequestException apiEx) when (apiEx.Message.Contains("message is not modified")) { // Message content hasn't changed, this is fine _logger.LogDebug("Attempted to edit message with identical content"); } } private async Task DisplayPaymentInfo(ITelegramBotClient bot, Message message, dynamic payment, string paymentText) { // Generate QR code if enabled if (_configuration.GetValue("Features:EnableQRCodes")) { try { using var qrGenerator = new QRCodeGenerator(); var qrCodeData = qrGenerator.CreateQrCode(payment.WalletAddress, QRCodeGenerator.ECCLevel.Q); using var qrCode = new PngByteQRCode(qrCodeData); var qrCodeBytes = qrCode.GetGraphic(10); using var stream = new System.IO.MemoryStream(qrCodeBytes); await bot.SendPhotoAsync( message.Chat.Id, InputFile.FromStream(stream, "payment_qr.png"), caption: paymentText, parseMode: Telegram.Bot.Types.Enums.ParseMode.Markdown ); // Delete the original message await bot.DeleteMessageAsync(message.Chat.Id, message.MessageId); } catch (Exception ex) { _logger.LogError(ex, "Failed to generate QR code"); // Fall back to text-only await SafeEditMessageAsync( bot, message.Chat.Id, message.MessageId, paymentText, Telegram.Bot.Types.Enums.ParseMode.Markdown, MenuBuilder.MainMenu() ); } } else { await SafeEditMessageAsync( bot, message.Chat.Id, message.MessageId, paymentText, Telegram.Bot.Types.Enums.ParseMode.Markdown, MenuBuilder.MainMenu() ); } } private async Task HandleViewOrders(ITelegramBotClient bot, Message message, UserSession session, User telegramUser) { // Use new customer-based order lookup var orders = await _shopService.GetCustomerOrdersAsync( telegramUser.Id, telegramUser.Username ?? "", $"{telegramUser.FirstName} {telegramUser.LastName}".Trim(), telegramUser.FirstName ?? "", telegramUser.LastName ?? "" ); if (!orders.Any()) { await bot.EditMessageTextAsync( message.Chat.Id, message.MessageId, "📦 *Your Orders*\n\nYou have no orders yet.", parseMode: Telegram.Bot.Types.Enums.ParseMode.Markdown, replyMarkup: MenuBuilder.MainMenu() ); } else { await bot.EditMessageTextAsync( message.Chat.Id, message.MessageId, $"📦 *Your Orders*\n\nFound {orders.Count} order(s):", parseMode: Telegram.Bot.Types.Enums.ParseMode.Markdown, replyMarkup: MenuBuilder.OrderListMenu(orders) ); } session.State = SessionState.ViewingOrders; } private async Task HandleViewOrder(ITelegramBotClient bot, Message message, UserSession session, Guid orderId, User telegramUser) { var order = await _shopService.GetCustomerOrderAsync( orderId, telegramUser.Id, telegramUser.Username ?? "", $"{telegramUser.FirstName} {telegramUser.LastName}".Trim(), telegramUser.FirstName ?? "", telegramUser.LastName ?? "" ); if (order == null) { await bot.AnswerCallbackQueryAsync("", "Order not found", showAlert: true); return; } await bot.EditMessageTextAsync( message.Chat.Id, message.MessageId, MessageFormatter.FormatOrder(order), parseMode: Telegram.Bot.Types.Enums.ParseMode.Markdown, replyMarkup: MenuBuilder.MainMenu() ); session.State = SessionState.ViewingOrder; } private async Task HandlePrivacySettings(ITelegramBotClient bot, Message message, UserSession session, string? setting) { if (setting != null) { switch (setting) { case "ephemeral": session.Privacy.UseEphemeralMode = !session.Privacy.UseEphemeralMode; session.IsEphemeral = session.Privacy.UseEphemeralMode; break; case "tor": session.Privacy.UseTorOnly = !session.Privacy.UseTorOnly; break; case "pgp": session.Privacy.RequirePGP = !session.Privacy.RequirePGP; if (session.Privacy.RequirePGP && string.IsNullOrEmpty(session.Privacy.PGPPublicKey)) { await bot.SendTextMessageAsync( message.Chat.Id, "Please set your PGP public key using:\n`/pgpkey YOUR_PUBLIC_KEY`", parseMode: Telegram.Bot.Types.Enums.ParseMode.Markdown ); } break; case "analytics": session.Privacy.DisableAnalytics = !session.Privacy.DisableAnalytics; break; case "disappearing": session.Privacy.EnableDisappearingMessages = !session.Privacy.EnableDisappearingMessages; break; case "delete": await _sessionManager.DeleteUserDataAsync(message.From!.Id); await bot.EditMessageTextAsync( message.Chat.Id, message.MessageId, "✅ *All your data has been deleted*\n\nYou can start fresh with /start", parseMode: Telegram.Bot.Types.Enums.ParseMode.Markdown ); return; } } await bot.EditMessageTextAsync( message.Chat.Id, message.MessageId, MessageFormatter.FormatPrivacyPolicy(), parseMode: Telegram.Bot.Types.Enums.ParseMode.Markdown, replyMarkup: MenuBuilder.PrivacyMenu(session.Privacy) ); session.State = SessionState.PrivacySettings; } private async Task HandleHelp(ITelegramBotClient bot, Message message) { await bot.EditMessageTextAsync( message.Chat.Id, message.MessageId, MessageFormatter.FormatHelp(), parseMode: Telegram.Bot.Types.Enums.ParseMode.Markdown, replyMarkup: MenuBuilder.MainMenu() ); } private async Task HandleSupportCallback(ITelegramBotClient bot, CallbackQuery callbackQuery, UserSession session) { session.State = SessionState.CustomerSupport; await ShowCustomerConversationInCallback(bot, callbackQuery, session); await bot.AnswerCallbackQueryAsync(callbackQuery.Id); } private async Task HandleRefreshConversation(ITelegramBotClient bot, CallbackQuery callbackQuery, UserSession session) { await ShowCustomerConversationInCallback(bot, callbackQuery, session); await bot.AnswerCallbackQueryAsync(callbackQuery.Id, "Messages refreshed"); } private async Task HandleExitChat(ITelegramBotClient bot, CallbackQuery callbackQuery, UserSession session) { session.State = SessionState.MainMenu; await bot.EditMessageTextAsync( callbackQuery.Message!.Chat.Id, callbackQuery.Message.MessageId, "Chat ended. How can I help you today?", replyMarkup: MenuBuilder.MainMenu() ); await bot.AnswerCallbackQueryAsync(callbackQuery.Id); } private async Task ShowCustomerConversationInCallback(ITelegramBotClient bot, CallbackQuery callbackQuery, UserSession session) { try { var user = callbackQuery.From; // Get conversation history for this customer var messages = await _shopService.GetCustomerConversationAsync( user.Id, user.Username ?? "", $"{user.FirstName} {user.LastName}".Trim(), user.FirstName ?? "", user.LastName ?? "" ); var conversationText = "💬 *Your Messages*\n\n"; if (messages?.Any() == true) { conversationText += "Recent conversation:\n\n"; foreach (var msg in messages.OrderBy(m => m.CreatedAt).TakeLast(8)) // Show last 8 messages { var isFromBusiness = msg.Direction == 0; // AdminToCustomer var sender = isFromBusiness ? "🏪 Shop" : "👤 You"; var time = msg.CreatedAt.ToString("MMM dd, HH:mm"); conversationText += $"*{sender}* _{time}_\n{msg.Content}\n\n"; } } else { conversationText += "No messages yet. Start a conversation by typing below!\n\n"; } conversationText += "_Type your message or use buttons below._"; await bot.EditMessageTextAsync( callbackQuery.Message!.Chat.Id, callbackQuery.Message.MessageId, conversationText, parseMode: Telegram.Bot.Types.Enums.ParseMode.Markdown, replyMarkup: MenuBuilder.ConversationMenu() ); } catch (Exception ex) { _logger.LogError(ex, "Error showing customer conversation in callback"); await bot.EditMessageTextAsync( callbackQuery.Message!.Chat.Id, callbackQuery.Message.MessageId, "💬 *Customer Messages*\n\nReady to chat with our support team!\n\n_Type your message below._", parseMode: Telegram.Bot.Types.Enums.ParseMode.Markdown, replyMarkup: MenuBuilder.ConversationMenu() ); } } private async Task HandleCancelSupport(ITelegramBotClient bot, CallbackQuery callbackQuery, UserSession session) { session.State = SessionState.MainMenu; await bot.EditMessageTextAsync( callbackQuery.Message!.Chat.Id, callbackQuery.Message.MessageId, "Customer support cancelled. How can I help you today?", replyMarkup: MenuBuilder.MainMenu() ); await bot.AnswerCallbackQueryAsync(callbackQuery.Id); } } }