package com.honey.honey.controller;

import com.honey.honey.config.TelegramProperties;
import com.honey.honey.dto.TelegramApiResponse;
import com.honey.honey.dto.PaymentWebhookRequest;
import com.honey.honey.model.UserA;
import com.honey.honey.service.FeatureSwitchService;
import com.honey.honey.service.PaymentService;
import com.honey.honey.service.TelegramBotApiService;
import com.honey.honey.service.UserService;
import jakarta.servlet.http.HttpServletRequest;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.client.HttpClientErrorException;
import org.telegram.telegrambots.meta.api.objects.Update;
import org.telegram.telegrambots.meta.api.objects.Message;
import org.telegram.telegrambots.meta.api.objects.User;
import org.telegram.telegrambots.meta.api.objects.ChatMemberUpdated;
import org.telegram.telegrambots.meta.api.objects.CallbackQuery;
import org.telegram.telegrambots.meta.api.objects.payments.PreCheckoutQuery;
import org.telegram.telegrambots.meta.api.objects.payments.SuccessfulPayment;
import org.telegram.telegrambots.meta.api.objects.replykeyboard.InlineKeyboardMarkup;
import org.telegram.telegrambots.meta.api.objects.replykeyboard.ReplyKeyboardMarkup;
import org.telegram.telegrambots.meta.api.objects.replykeyboard.buttons.InlineKeyboardButton;
import org.telegram.telegrambots.meta.api.objects.replykeyboard.buttons.KeyboardButton;
import org.telegram.telegrambots.meta.api.objects.replykeyboard.buttons.KeyboardRow;
import org.telegram.telegrambots.meta.api.objects.webapp.WebAppInfo;
import com.honey.honey.service.LocalizationService;
import com.honey.honey.config.LocaleConfig;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.beans.factory.annotation.Value;
import java.util.List;
import java.util.ArrayList;
import java.util.Locale;

import java.util.HashMap;
import java.util.Map;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.Resource;
import org.springframework.util.StreamUtils;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.core.io.ByteArrayResource;

/**
 * Webhook controller for receiving Telegram updates directly.
 * Path: POST /api/telegram/webhook/{token}. Token must match APP_TELEGRAM_WEBHOOK_TOKEN.
 */
@Slf4j
@RestController
@RequestMapping("/api/telegram/webhook")
@RequiredArgsConstructor
public class TelegramWebhookController {

    private static final String MINI_APP_URL = "https://testforapp.website/test/auth";

    @Value("${app.telegram-webhook.token:}")
    private String expectedWebhookToken;

    private final UserService userService;
    private final PaymentService paymentService;
    private final TelegramProperties telegramProperties;
    private final LocalizationService localizationService;
    private final TelegramBotApiService telegramBotApiService;
    private final FeatureSwitchService featureSwitchService;
    private final ObjectMapper objectMapper = new ObjectMapper();

    /**
     * Webhook endpoint for receiving updates from Telegram.
     * Path token must match app.telegram-webhook.token (APP_TELEGRAM_WEBHOOK_TOKEN).
     */
    @PostMapping("/{token}")
    public ResponseEntity<?> handleWebhook(@PathVariable String token, @RequestBody Update update, HttpServletRequest httpRequest) {
        if (expectedWebhookToken.isEmpty() || !expectedWebhookToken.equals(token)) {
            log.warn("Webhook rejected: invalid or missing token (possible misconfiguration or wrong URL); update dropped");
            return ResponseEntity.status(HttpStatus.FORBIDDEN).build();
        }
        try {
            // Bot blocked / unblocked in private chat (Telegram my_chat_member)
            if (update.hasMyChatMember()) {
                handleMyChatMember(update.getMyChatMember());
                return ResponseEntity.ok().build();
            }

            // Handle callback queries (button clicks)
            if (update.hasCallbackQuery()) {
                handleCallbackQuery(update.getCallbackQuery());
            }
            
            // Handle message updates (e.g., /start command or Reply Keyboard button clicks)
            if (update.hasMessage() && update.getMessage().hasText()) {
                handleMessage(update.getMessage(), httpRequest);
            }
            
            // Handle pre-checkout query (before payment confirmation)
            if (update.hasPreCheckoutQuery()) {
                handlePreCheckoutQuery(update.getPreCheckoutQuery());
            }
            
            // Handle successful payment
            if (update.hasMessage() && update.getMessage().hasSuccessfulPayment()) {
                handleSuccessfulPayment(update.getMessage().getSuccessfulPayment(), update.getMessage().getFrom().getId());
            }
            
            return ResponseEntity.ok().build();
        } catch (Exception e) {
            log.error("Error processing Telegram webhook: {}", e.getMessage(), e);
            if (update.hasMessage() && update.getMessage().hasText() && update.getMessage().getText().startsWith("/start")) {
                Long telegramId = update.getMessage().getFrom() != null ? update.getMessage().getFrom().getId() : null;
                log.warn("Registration attempt failed (webhook error), update dropped: telegramId={}", telegramId);
            }
            return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build();
        }
    }

    /**
     * Updates {@code bot_active} when the user blocks/unblocks the bot (private chats only).
     */
    private void handleMyChatMember(ChatMemberUpdated cmu) {
        if (cmu.getChat() == null || cmu.getNewChatMember() == null) {
            return;
        }
        if (!"private".equalsIgnoreCase(cmu.getChat().getType())) {
            return;
        }
        Long telegramUserId = cmu.getChat().getId();
        String status = cmu.getNewChatMember().getStatus();
        if ("kicked".equalsIgnoreCase(status) || "left".equalsIgnoreCase(status)) {
            userService.setBotActiveByTelegramId(telegramUserId, false);
            log.info("Private chat bot status: user {} set bot_active=false (status={})", telegramUserId, status);
        } else if ("member".equalsIgnoreCase(status) || "administrator".equalsIgnoreCase(status)) {
            userService.setBotActiveByTelegramId(telegramUserId, true);
            log.info("Private chat bot status: user {} set bot_active=true (status={})", telegramUserId, status);
        }
    }

    /**
     * Handles /start command with optional referral parameter, and Reply Keyboard button clicks.
     * Format: /start or /start 123 (where 123 is the referral user ID)
     */
    private void handleMessage(Message message, HttpServletRequest httpRequest) {
        String messageText = message.getText();
        if (messageText == null) {
            return;
        }
        
        User telegramUser = message.getFrom();
        Long telegramId = telegramUser.getId();
        Long chatId = message.getChatId();
        
        // Handle /start command
        if (messageText.startsWith("/start")) {
            handleStartCommand(message, httpRequest, telegramUser, telegramId);
            return;
        }
        
        // Handle Reply Keyboard button clicks
        // Priority: 1) saved user language, 2) Telegram user's language_code, 3) default EN
        String languageCode = "EN"; // Default
        try {
            var userOpt = userService.getUserByTelegramId(telegramId);
            if (userOpt.isPresent() && userOpt.get().getLanguageCode() != null) {
                languageCode = userOpt.get().getLanguageCode();
            } else if (telegramUser.getLanguageCode() != null && !telegramUser.getLanguageCode().isEmpty()) {
                languageCode = telegramUser.getLanguageCode();
            }
        } catch (Exception e) {
            log.warn("Could not get user language, using default: {}", e.getMessage());
            // Fallback to Telegram user's language_code if available
            if (telegramUser.getLanguageCode() != null && !telegramUser.getLanguageCode().isEmpty()) {
                languageCode = telegramUser.getLanguageCode();
            }
        }
        Locale locale = LocaleConfig.languageCodeToLocale(languageCode);
        
        // Check if message matches Reply Keyboard button text
        String startSpinningText = "🎰 " + localizationService.getMessage(locale, "bot.button.startSpinning");
        String usersPayoutsText = "💸 " + localizationService.getMessage(locale, "bot.button.usersPayouts");
        String infoChannelText = "ℹ️ " + localizationService.getMessage(locale, "bot.button.infoChannel");
        
        if (messageText.equals(startSpinningText)) {
            sendStartSpinningMessage(chatId, locale);
        } else if (messageText.equals(usersPayoutsText)) {
            sendUsersPayoutsMessage(chatId, locale);
        } else if (messageText.equals(infoChannelText)) {
            sendInfoChannelMessage(chatId, locale);
        } else {
            // Unknown message (e.g. old "Start Spinning" button or free text): reply and refresh keyboard
            sendUnrecognizedMessageAndUpdateKeyboard(chatId, locale);
        }
    }
    
    /**
     * Handles /start command with optional referral parameter.
     * Format: /start or /start 123 (where 123 is the referral user ID)
     */
    private void handleStartCommand(Message message, HttpServletRequest httpRequest, User telegramUser, Long telegramId) {
        String messageText = message.getText();

        log.debug("Received /start command: telegramId={}", telegramId);

        Integer referralUserId = null;

        // Parse referral parameter from /start command
        // Format: /start or /start 123
        String[] parts = messageText.split("\\s+", 2);
        if (parts.length > 1 && !parts[1].trim().isEmpty()) {
            try {
                referralUserId = Integer.parseInt(parts[1].trim());
                log.debug("Parsed referral ID: {}", referralUserId);
            } catch (NumberFormatException e) {
                log.warn("Invalid referral parameter format: '{}'", parts[1]);
                return;
            }
        }

        // Check if user already exists
        boolean isNewUser = userService.getUserByTelegramId(telegramId).isEmpty();
        if (isNewUser) {
            log.info("New user registration via bot - telegramId={}, referralUserId={}", 
                    telegramId, referralUserId);
        }

        // Build tgUserData map similar to what TelegramAuthService.parseInitData returns
        Map<String, Object> tgUser = new HashMap<>();
        tgUser.put("id", telegramId);
        tgUser.put("first_name", telegramUser.getFirstName());
        tgUser.put("last_name", telegramUser.getLastName());
        tgUser.put("username", telegramUser.getUserName());
        tgUser.put("is_premium", telegramUser.getIsPremium() != null && telegramUser.getIsPremium());
        tgUser.put("language_code", telegramUser.getLanguageCode());
        // Note: Telegram Bot API User object doesn't have photo_url (only available in WebApp initData)
        // AvatarService will fetch avatar from Bot API first, photo_url is only used as fallback
        tgUser.put("photo_url", null);

        Map<String, Object> tgUserData = new HashMap<>();
        tgUserData.put("user", tgUser);
        
        // Convert referralUserId to start parameter string (as expected by UserService)
        String start = referralUserId != null ? String.valueOf(referralUserId) : null;
        tgUserData.put("start", start);

        try {
            // Get or create user (handles registration, login update, and referral system)
            UserA user = userService.getOrCreateUser(tgUserData, httpRequest);
            userService.setBotActiveByTelegramId(telegramId, true);
            log.debug("Bot registration completed: userId={}, telegramId={}, isNewUser={}", 
                    user.getId(), user.getTelegramId(), isNewUser);
            
            // Send welcome message with buttons
            // Priority: 1) saved user language, 2) Telegram user's language_code, 3) default EN
            String languageCode = "EN"; // Default
            if (user.getLanguageCode() != null && !user.getLanguageCode().isEmpty()) {
                languageCode = user.getLanguageCode();
            } else if (telegramUser.getLanguageCode() != null && !telegramUser.getLanguageCode().isEmpty()) {
                languageCode = telegramUser.getLanguageCode();
            }
            sendWelcomeMessage(telegramId, languageCode);
        } catch (Exception e) {
            log.warn("Registration failed for telegramId={}, user may not receive welcome message: {}", telegramId, e.getMessage());
            log.error("Error registering user via bot: telegramId={}", telegramId, e);
        }
    }
    
    /**
     * Handles callback queries from inline keyboard buttons.
     */
    private void handleCallbackQuery(CallbackQuery callbackQuery) {
        String data = callbackQuery.getData();
        Long telegramUserId = callbackQuery.getFrom().getId();
        Long chatId = callbackQuery.getMessage().getChatId();
        
        log.debug("Received callback query: data={}, telegramUserId={}", data, telegramUserId);
        
        // Get user's language for localization
        // Priority: 1) saved user language, 2) Telegram user's language_code, 3) default EN
        User telegramUser = callbackQuery.getFrom();
        String languageCode = "EN"; // Default
        try {
            var userOpt = userService.getUserByTelegramId(telegramUserId);
            if (userOpt.isPresent() && userOpt.get().getLanguageCode() != null) {
                languageCode = userOpt.get().getLanguageCode();
            } else if (telegramUser.getLanguageCode() != null && !telegramUser.getLanguageCode().isEmpty()) {
                languageCode = telegramUser.getLanguageCode();
            }
        } catch (Exception e) {
            log.warn("Could not get user language, using default: {}", e.getMessage());
            // Fallback to Telegram user's language_code if available
            if (telegramUser.getLanguageCode() != null && !telegramUser.getLanguageCode().isEmpty()) {
                languageCode = telegramUser.getLanguageCode();
            }
        }
        
        Locale locale = LocaleConfig.languageCodeToLocale(languageCode);
        
        try {
            switch (data) {
                case "start_spinning":
                    sendStartSpinningMessage(chatId, locale);
                    answerCallbackQuery(callbackQuery.getId(), null);
                    break;
                case "users_payouts":
                    sendUsersPayoutsMessage(chatId, locale);
                    answerCallbackQuery(callbackQuery.getId(), null);
                    break;
                case "info_channel":
                    sendInfoChannelMessage(chatId, locale);
                    answerCallbackQuery(callbackQuery.getId(), null);
                    break;
                default:
                    log.warn("Unknown callback data: {}", data);
                    answerCallbackQuery(callbackQuery.getId(), "Unknown action");
            }
        } catch (Exception e) {
            log.error("Error handling callback query: data={}", data, e);
            answerCallbackQuery(callbackQuery.getId(), "Error processing request");
        }
    }
    
    /**
     * Builds the current Reply Keyboard (Start Game, Users payouts, Info channel) for the given locale.
     */
    private ReplyKeyboardMarkup buildReplyKeyboard(Locale locale) {
        ReplyKeyboardMarkup replyKeyboard = new ReplyKeyboardMarkup();
        replyKeyboard.setResizeKeyboard(true);
        replyKeyboard.setOneTimeKeyboard(false);
        replyKeyboard.setSelective(false);
        List<KeyboardRow> keyboardRows = new ArrayList<>();
        KeyboardRow row1 = new KeyboardRow();
        KeyboardButton startButton = new KeyboardButton();
        startButton.setText("🎰 " + localizationService.getMessage(locale, "bot.button.startSpinning"));
        row1.add(startButton);
        keyboardRows.add(row1);
        KeyboardRow row2 = new KeyboardRow();
        KeyboardButton payoutsButton = new KeyboardButton();
        payoutsButton.setText("💸 " + localizationService.getMessage(locale, "bot.button.usersPayouts"));
        row2.add(payoutsButton);
        KeyboardButton infoButton = new KeyboardButton();
        infoButton.setText("ℹ️ " + localizationService.getMessage(locale, "bot.button.infoChannel"));
        row2.add(infoButton);
        keyboardRows.add(row2);
        replyKeyboard.setKeyboard(keyboardRows);
        return replyKeyboard;
    }

    /**
     * Sends welcome messages: first message with reply keyboard, second message with inline button.
     */
    private void sendWelcomeMessage(Long chatId, String languageCode) {
        if (telegramProperties.getBotToken() == null || telegramProperties.getBotToken().isEmpty()) {
            log.warn("Bot token not configured; welcome message not sent for chatId={} (registration flow affected)", chatId);
            return;
        }
        Locale locale = LocaleConfig.languageCodeToLocale(languageCode != null ? languageCode : "EN");
        ReplyKeyboardMarkup replyKeyboard = buildReplyKeyboard(locale);
        String firstMessage = localizationService.getMessage(locale, "bot.welcome.firstMessage");
        sendAnimationWithReplyKeyboard(chatId, firstMessage, replyKeyboard);

        String welcomeText = localizationService.getMessage(locale, "bot.welcome.message");
        if (featureSwitchService.isStartGameButtonEnabled()) {
            InlineKeyboardMarkup inlineKeyboard = new InlineKeyboardMarkup();
            List<List<InlineKeyboardButton>> inlineRows = new ArrayList<>();
            List<InlineKeyboardButton> inlineRow = new ArrayList<>();
            InlineKeyboardButton startInlineButton = new InlineKeyboardButton();
            String startSpinningButtonText = localizationService.getMessage(locale, "bot.button.startSpinningInline");
            startInlineButton.setText(startSpinningButtonText);
            WebAppInfo webAppInfo = new WebAppInfo();
            webAppInfo.setUrl(MINI_APP_URL);
            startInlineButton.setWebApp(webAppInfo);
            inlineRow.add(startInlineButton);
            inlineRows.add(inlineRow);
            inlineKeyboard.setKeyboard(inlineRows);
            sendMessage(chatId, welcomeText, inlineKeyboard);
        } else {
            sendMessage(chatId, welcomeText, null);
        }
    }
    
    /**
     * Sends message with Start Spinning button.
     */
    private void sendStartSpinningMessage(Long chatId, Locale locale) {
        String message = localizationService.getMessage(locale, "bot.message.startSpinning");
        if (!featureSwitchService.isStartGameButtonEnabled()) {
            sendMessage(chatId, message, null);
            return;
        }
        InlineKeyboardMarkup keyboard = new InlineKeyboardMarkup();
        List<List<InlineKeyboardButton>> rows = new ArrayList<>();
        List<InlineKeyboardButton> row = new ArrayList<>();
        InlineKeyboardButton button = new InlineKeyboardButton();
        String startSpinningButtonText = localizationService.getMessage(locale, "bot.button.startSpinningInline");
        button.setText(startSpinningButtonText);
        WebAppInfo webAppInfo = new WebAppInfo();
        webAppInfo.setUrl(MINI_APP_URL);
        button.setWebApp(webAppInfo);
        row.add(button);
        rows.add(row);
        keyboard.setKeyboard(rows);
        sendMessage(chatId, message, keyboard);
    }

    /**
     * Sends a friendly "unrecognized message" reply and updates the user's reply keyboard to the current one.
     * Used when the user sends unknown text (e.g. old "Start Spinning" button) so they get the new keyboard.
     */
    private void sendUnrecognizedMessageAndUpdateKeyboard(Long chatId, Locale locale) {
        String message = localizationService.getMessage(locale, "bot.message.unrecognized");
        ReplyKeyboardMarkup replyKeyboard = buildReplyKeyboard(locale);
        sendMessageWithReplyKeyboard(chatId, message, replyKeyboard);
    }

    /**
     * Sends message with Users payouts button.
     */
    private void sendUsersPayoutsMessage(Long chatId, Locale locale) {
        String message = localizationService.getMessage(locale, "bot.message.usersPayouts");
        
        InlineKeyboardMarkup keyboard = new InlineKeyboardMarkup();
        List<List<InlineKeyboardButton>> rows = new ArrayList<>();
        List<InlineKeyboardButton> row = new ArrayList<>();
        
        InlineKeyboardButton button = new InlineKeyboardButton();
        button.setText(localizationService.getMessage(locale, "bot.button.openChannel"));
        button.setUrl("https://t.me/win_spin_withdrawals");
        row.add(button);
        rows.add(row);
        
        keyboard.setKeyboard(rows);
        
        sendMessage(chatId, message, keyboard);
    }
    
    /**
     * Handles /paysupport command.
     */
    private void handlePaySupportCommand(Long chatId, User telegramUser, Long telegramId) {
        // Get user's language for localization
        // Priority: 1) saved user language, 2) Telegram user's language_code, 3) default EN
        String languageCode = "EN"; // Default
        try {
            var userOpt = userService.getUserByTelegramId(telegramId);
            if (userOpt.isPresent() && userOpt.get().getLanguageCode() != null) {
                languageCode = userOpt.get().getLanguageCode();
            } else if (telegramUser.getLanguageCode() != null && !telegramUser.getLanguageCode().isEmpty()) {
                languageCode = telegramUser.getLanguageCode();
            }
        } catch (Exception e) {
            log.warn("Could not get user language for /paysupport, using default: {}", e.getMessage());
            // Fallback to Telegram user's language_code if available
            if (telegramUser.getLanguageCode() != null && !telegramUser.getLanguageCode().isEmpty()) {
                languageCode = telegramUser.getLanguageCode();
            }
        }
        
        Locale locale = LocaleConfig.languageCodeToLocale(languageCode);
        String message = localizationService.getMessage(locale, "bot.message.paySupport");
        sendMessage(chatId, message, null);
    }
    
    /**
     * Sends message with Info channel button.
     */
    private void sendInfoChannelMessage(Long chatId, Locale locale) {
        String message = localizationService.getMessage(locale, "bot.message.infoChannel");
        
        InlineKeyboardMarkup keyboard = new InlineKeyboardMarkup();
        List<List<InlineKeyboardButton>> rows = new ArrayList<>();
        List<InlineKeyboardButton> row = new ArrayList<>();
        
        InlineKeyboardButton button = new InlineKeyboardButton();
        button.setText(localizationService.getMessage(locale, "bot.button.goToChannel"));
        button.setUrl("https://t.me/win_spin_news");
        row.add(button);
        rows.add(row);
        
        keyboard.setKeyboard(rows);
        
        sendMessage(chatId, message, keyboard);
    }
    
    /**
     * Sends a message to a chat with inline keyboard.
     */
    private void sendMessage(Long chatId, String text, InlineKeyboardMarkup keyboard) {
        String botToken = telegramProperties.getBotToken();
        if (botToken == null || botToken.isEmpty()) {
            log.error("Bot token is not configured");
            return;
        }
        
        String url = "https://api.telegram.org/bot" + botToken + "/sendMessage";
        
        Map<String, Object> requestBody = new HashMap<>();
        requestBody.put("chat_id", chatId);
        requestBody.put("text", text);
        if (keyboard != null) {
            // Convert InlineKeyboardMarkup to Map for JSON serialization
            try {
                String keyboardJson = objectMapper.writeValueAsString(keyboard);
                Map<String, Object> keyboardMap = objectMapper.readValue(keyboardJson, Map.class);
                requestBody.put("reply_markup", keyboardMap);
            } catch (Exception e) {
                log.error("Error serializing keyboard: {}", e.getMessage(), e);
                return;
            }
        }
        
        HttpHeaders headers = new HttpHeaders();
        headers.setContentType(MediaType.APPLICATION_JSON);
        
        HttpEntity<Map<String, Object>> entity = new HttpEntity<>(requestBody, headers);
        
        try {
            ResponseEntity<TelegramApiResponse> response = telegramBotApiService.post(url, entity);
            if (response != null && response.getBody() != null) {
                if (!Boolean.TRUE.equals(response.getBody().getOk())) {
                    log.warn("Failed to send message: chatId={}, error={}",
                            chatId, response.getBody().getDescription());
                }
            } else if (response != null && !response.getStatusCode().is2xxSuccessful()) {
                log.error("Failed to send message: chatId={}, status={}", chatId, response.getStatusCode());
            } else if (response == null) {
                log.warn("Message not sent (Telegram 429, retry scheduled): chatId={} – may affect registration welcome", chatId);
            }
        } catch (Exception e) {
            if (isTelegramUserUnavailable(e)) {
                log.warn("Cannot send message: user blocked bot or chat unavailable: chatId={}", chatId);
            } else {
                log.error("Error sending message: chatId={}", chatId, e);
            }
        }
    }
    
    /**
     * Returns true if the failure is due to user blocking the bot or chat being unavailable.
     * These are expected and should be logged at WARN without stack trace.
     */
    private boolean isTelegramUserUnavailable(Throwable t) {
        if (t instanceof HttpClientErrorException e) {
            if (e.getStatusCode().value() == 403) {
                return true;
            }
            String body = e.getResponseBodyAsString();
            return body != null && (
                body.contains("blocked by the user") ||
                body.contains("user is deactivated") ||
                body.contains("chat not found")
            );
        }
        return false;
    }

    private boolean isTelegramUserUnavailableDescription(String description) {
        return description != null && (
            description.contains("blocked by the user") ||
            description.contains("user is deactivated") ||
            description.contains("chat not found")
        );
    }

    /**
     * Sends a message with text and reply keyboard.
     */
    private void sendMessageWithReplyKeyboard(Long chatId, String text, ReplyKeyboardMarkup replyKeyboard) {
        String botToken = telegramProperties.getBotToken();
        if (botToken == null || botToken.isEmpty()) {
            log.error("Bot token is not configured");
            return;
        }
        
        String url = "https://api.telegram.org/bot" + botToken + "/sendMessage";
        
        Map<String, Object> requestBody = new HashMap<>();
        requestBody.put("chat_id", chatId);
        requestBody.put("text", text);
        
        try {
            String keyboardJson = objectMapper.writeValueAsString(replyKeyboard);
            Map<String, Object> keyboardMap = objectMapper.readValue(keyboardJson, Map.class);
            requestBody.put("reply_markup", keyboardMap);
        } catch (Exception e) {
            log.error("Error serializing reply keyboard: {}", e.getMessage(), e);
            return;
        }
        
        HttpHeaders headers = new HttpHeaders();
        headers.setContentType(MediaType.APPLICATION_JSON);
        
        HttpEntity<Map<String, Object>> entity = new HttpEntity<>(requestBody, headers);
        
        try {
            ResponseEntity<TelegramApiResponse> response = telegramBotApiService.post(url, entity);
            if (response != null && response.getBody() != null) {
                if (!Boolean.TRUE.equals(response.getBody().getOk())) {
                    String desc = response.getBody().getDescription();
                    if (isTelegramUserUnavailableDescription(desc)) {
                        log.warn("Cannot send message: user blocked bot or chat unavailable: chatId={}", chatId);
                    } else {
                        log.error("Failed to send message with reply keyboard: chatId={}, error={}", chatId, desc);
                    }
                } else {
                    log.info("Message with reply keyboard sent successfully: chatId={}", chatId);
                }
            } else if (response != null && !response.getStatusCode().is2xxSuccessful()) {
                log.error("Failed to send message with reply keyboard: chatId={}, status={}",
                        chatId, response.getStatusCode());
            }
        } catch (Exception e) {
            if (isTelegramUserUnavailable(e)) {
                log.warn("Cannot send message: user blocked bot or chat unavailable: chatId={}", chatId);
            } else {
                log.error("Error sending message with reply keyboard: chatId={}", chatId, e);
            }
        }
    }
    
    /**
     * Sends an animation (MP4 video) with caption text and reply keyboard.
     * Uses MP4 format as Telegram handles silent MP4s better than GIF files.
     */
    private void sendAnimationWithReplyKeyboard(Long chatId, String caption, ReplyKeyboardMarkup replyKeyboard) {
        String botToken = telegramProperties.getBotToken();
        if (botToken == null || botToken.isEmpty()) {
            log.error("Bot token is not configured");
            return;
        }
        
        String url = "https://api.telegram.org/bot" + botToken + "/sendAnimation";
        
        try {
            // Load MP4 from resources (Telegram "GIFs" are actually silent MP4 videos)
            Resource resource = new ClassPathResource("assets/winspin_5.mp4");
            if (!resource.exists()) {
                log.error("MP4 file not found: assets/winspin_5.mp4");
                // Fallback to text message if MP4 not found
                sendMessageWithReplyKeyboard(chatId, caption, replyKeyboard);
                return;
            }
            
            byte[] videoBytes = StreamUtils.copyToByteArray(resource.getInputStream());
            ByteArrayResource videoResource = new ByteArrayResource(videoBytes) {
                @Override
                public String getFilename() {
                    return "winspin_5.mp4";
                }
            };
            
            // Create multipart form data
            MultiValueMap<String, Object> body = new LinkedMultiValueMap<>();
            body.add("chat_id", chatId.toString());
            body.add("caption", caption);
            
            // EXPLICITLY SET MIME TYPE FOR THE ANIMATION PART
            // This is crucial - Telegram needs to know it's a video/mp4
            HttpHeaders fileHeaders = new HttpHeaders();
            fileHeaders.setContentType(MediaType.parseMediaType("video/mp4"));
            HttpEntity<ByteArrayResource> filePart = new HttpEntity<>(videoResource, fileHeaders);
            body.add("animation", filePart);
            
            // Add reply keyboard if provided
            if (replyKeyboard != null) {
                try {
                    String keyboardJson = objectMapper.writeValueAsString(replyKeyboard);
                    body.add("reply_markup", keyboardJson);
                } catch (Exception e) {
                    log.error("Error serializing reply keyboard: {}", e.getMessage(), e);
                }
            }
            
            HttpHeaders headers = new HttpHeaders();
            headers.setContentType(MediaType.MULTIPART_FORM_DATA);
            
            HttpEntity<MultiValueMap<String, Object>> entity = new HttpEntity<>(body, headers);
            
            ResponseEntity<TelegramApiResponse> response = telegramBotApiService.post(url, entity);
            if (response != null && response.getBody() != null) {
                if (!Boolean.TRUE.equals(response.getBody().getOk())) {
                    String desc = response.getBody().getDescription();
                    if (isTelegramUserUnavailableDescription(desc)) {
                        log.warn("Cannot send animation: user blocked bot or chat unavailable: chatId={}", chatId);
                    } else {
                        log.error("Failed to send animation with reply keyboard: chatId={}, error={}", chatId, desc);
                        sendMessageWithReplyKeyboard(chatId, caption, replyKeyboard);
                    }
                } else {
                    log.info("Animation with reply keyboard sent successfully: chatId={}", chatId);
                }
            } else if (response != null && !response.getStatusCode().is2xxSuccessful()) {
                log.error("Failed to send animation with reply keyboard: chatId={}, status={}",
                        chatId, response.getStatusCode());
                sendMessageWithReplyKeyboard(chatId, caption, replyKeyboard);
            } else if (response == null) {
                log.warn("Welcome animation delayed (Telegram 429, retry scheduled): chatId={} – registration flow may appear incomplete", chatId);
            }
        } catch (Exception e) {
            if (isTelegramUserUnavailable(e)) {
                log.warn("Cannot send animation: user blocked bot or chat unavailable: chatId={}", chatId);
            } else {
                log.error("Error sending animation with reply keyboard: chatId={}", chatId, e);
                sendMessageWithReplyKeyboard(chatId, caption, replyKeyboard);
            }
        }
    }
    
    /**
     * Sends a message with only reply keyboard (for setting up persistent keyboard).
     */
    private void sendReplyKeyboardOnly(Long chatId, ReplyKeyboardMarkup replyKeyboard) {
        String botToken = telegramProperties.getBotToken();
        if (botToken == null || botToken.isEmpty()) {
            log.error("Bot token is not configured");
            return;
        }
        
        String url = "https://api.telegram.org/bot" + botToken + "/sendMessage";
        
        Map<String, Object> requestBody = new HashMap<>();
        requestBody.put("chat_id", chatId);
        // Telegram requires non-empty text for messages with reply keyboard
        // Sending with a minimal message - this message won't be visible to users
        // but is required to set up the persistent keyboard
        requestBody.put("text", ".");
        
        try {
            String keyboardJson = objectMapper.writeValueAsString(replyKeyboard);
            Map<String, Object> keyboardMap = objectMapper.readValue(keyboardJson, Map.class);
            requestBody.put("reply_markup", keyboardMap);
        } catch (Exception e) {
            log.error("Error serializing reply keyboard: {}", e.getMessage(), e);
            return;
        }
        
        HttpHeaders headers = new HttpHeaders();
        headers.setContentType(MediaType.APPLICATION_JSON);
        
        HttpEntity<Map<String, Object>> entity = new HttpEntity<>(requestBody, headers);
        
        try {
            ResponseEntity<TelegramApiResponse> response = telegramBotApiService.post(url, entity);
            if (response != null && response.getBody() != null) {
                if (!Boolean.TRUE.equals(response.getBody().getOk())) {
                    String desc = response.getBody().getDescription();
                    if (isTelegramUserUnavailableDescription(desc)) {
                        log.warn("Cannot send reply keyboard: user blocked bot or chat unavailable: chatId={}", chatId);
                    } else {
                        log.error("Failed to send reply keyboard: chatId={}, error={}", chatId, desc);
                    }
                } else {
                    log.info("Reply keyboard sent successfully: chatId={}", chatId);
                }
            } else if (response != null && !response.getStatusCode().is2xxSuccessful()) {
                log.error("Failed to send reply keyboard: chatId={}, status={}",
                        chatId, response.getStatusCode());
            }
        } catch (Exception e) {
            if (isTelegramUserUnavailable(e)) {
                log.warn("Cannot send reply keyboard: user blocked bot or chat unavailable: chatId={}", chatId);
            } else {
                log.error("Error sending reply keyboard: chatId={}", chatId, e);
            }
        }
    }
    
    /**
     * Answers a callback query.
     */
    private void answerCallbackQuery(String queryId, String text) {
        String botToken = telegramProperties.getBotToken();
        if (botToken == null || botToken.isEmpty()) {
            log.error("Bot token is not configured");
            return;
        }
        
        String url = "https://api.telegram.org/bot" + botToken + "/answerCallbackQuery";
        
        Map<String, Object> requestBody = new HashMap<>();
        requestBody.put("callback_query_id", queryId);
        if (text != null) {
            requestBody.put("text", text);
        }
        
        HttpHeaders headers = new HttpHeaders();
        headers.setContentType(MediaType.APPLICATION_JSON);
        
        HttpEntity<Map<String, Object>> entity = new HttpEntity<>(requestBody, headers);
        
        try {
            ResponseEntity<TelegramApiResponse> response = telegramBotApiService.post(url, entity);
            if (response != null && response.getBody() != null && !Boolean.TRUE.equals(response.getBody().getOk())) {
                log.warn("Failed to answer callback query: queryId={}, error={}",
                        queryId, response.getBody().getDescription());
            }
        } catch (Exception e) {
            log.error("Error answering callback query: queryId={}", queryId, e);
        }
    }

    /**
     * Handles pre-checkout query (before payment confirmation).
     * Telegram sends this to verify the payment before the user confirms.
     * We must answer it to approve the payment, otherwise it will expire.
     */
    private void handlePreCheckoutQuery(PreCheckoutQuery preCheckoutQuery) {
        String queryId = preCheckoutQuery.getId();
        String invoicePayload = preCheckoutQuery.getInvoicePayload(); // This is our orderId
        
        log.debug("Pre-checkout query: queryId={}, orderId={}", queryId, invoicePayload);
        
        // Answer the pre-checkout query to approve the payment
        // We always approve since we validate on successful payment
        answerPreCheckoutQuery(queryId, true, null);
    }
    
    /**
     * Answers a pre-checkout query to approve or reject the payment.
     * 
     * @param queryId The pre-checkout query ID
     * @param ok True to approve, false to reject
     * @param errorMessage Error message if rejecting (null if approving)
     */
    private void answerPreCheckoutQuery(String queryId, boolean ok, String errorMessage) {
        String botToken = telegramProperties.getBotToken();
        if (botToken == null || botToken.isEmpty()) {
            log.error("Bot token is not configured");
            return;
        }

        String url = "https://api.telegram.org/bot" + botToken + "/answerPreCheckoutQuery";

        Map<String, Object> requestBody = new HashMap<>();
        requestBody.put("pre_checkout_query_id", queryId);
        requestBody.put("ok", ok);
        if (!ok && errorMessage != null) {
            requestBody.put("error_message", errorMessage);
        }

        HttpHeaders headers = new HttpHeaders();
        headers.setContentType(MediaType.APPLICATION_JSON);

        HttpEntity<Map<String, Object>> entity = new HttpEntity<>(requestBody, headers);

        try {
            log.debug("Answering pre-checkout query: queryId={}, ok={}", queryId, ok);
            ResponseEntity<TelegramApiResponse> response = telegramBotApiService.post(url, entity);
            if (response != null && response.getBody() != null && !Boolean.TRUE.equals(response.getBody().getOk())) {
                log.warn("Failed to answer pre-checkout query: queryId={}, error={}",
                        queryId, response.getBody().getDescription());
            } else if (response != null && !response.getStatusCode().is2xxSuccessful()) {
                log.error("Failed to answer pre-checkout query: queryId={}, status={}", queryId, response.getStatusCode());
            }
        } catch (Exception e) {
            log.error("Error answering pre-checkout query: queryId={}", queryId, e);
        }
    }

    /**
     * Handles successful payment from Telegram.
     * Processes the payment and credits the user's balance.
     */
    private void handleSuccessfulPayment(SuccessfulPayment successfulPayment, Long telegramUserId) {
        String invoicePayload = successfulPayment.getInvoicePayload(); // This is our orderId
        
        // Extract stars amount from total amount
        // Telegram sends amount in the smallest currency unit (for Stars, it's 1:1)
        Integer starsAmount = successfulPayment.getTotalAmount().intValue();

        log.info("Payment webhook received: orderId={}, telegramUserId={}, starsAmount={}", invoicePayload, telegramUserId, starsAmount);

        try {
            // Create webhook request and process payment
            PaymentWebhookRequest request = new PaymentWebhookRequest();
            request.setOrderId(invoicePayload);
            request.setTelegramUserId(telegramUserId);
            request.setTelegramPaymentChargeId(successfulPayment.getTelegramPaymentChargeId());
            request.setTelegramProviderPaymentChargeId(successfulPayment.getProviderPaymentChargeId());
            request.setStarsAmount(starsAmount);

            boolean processed = paymentService.processPaymentWebhook(request);
            if (!processed) {
                log.warn("Payment already processed: orderId={}", invoicePayload);
            }
        } catch (Exception e) {
            log.error("Error processing payment webhook: orderId={}", invoicePayload, e);
        }
    }
}

