package kz.konturai.parser.service;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import kz.konturai.parser.exception.TelegramTokenExpiredException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.io.ByteArrayResource;
import org.springframework.http.MediaType;
import org.springframework.http.client.reactive.ReactorClientHttpConnector;
import org.springframework.stereotype.Service;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.web.reactive.function.BodyInserters;
import org.springframework.web.reactive.function.client.WebClient;
import org.springframework.web.reactive.function.client.WebClientResponseException;
import reactor.netty.http.client.HttpClient;

import java.time.Duration;
import java.util.List;

@Service
public class TelegramPostingService {

    private static final Logger logger = LoggerFactory.getLogger(TelegramPostingService.class);
    private static final String TELEGRAM_API_BASE = "https://api.telegram.org/bot";

    private final ObjectMapper objectMapper;

    @Value("${telegram.api.timeout:30000}")
    private int timeoutMs;

    public TelegramPostingService() {
        this.objectMapper = new ObjectMapper();
    }

    /**
     * Публикует пост в Telegram через Bot API
     * 
     * @param credentials JSON строка с botToken и chatId: {"botToken": "...", "chatId": "..."}
     * @param postText    Текст поста
     * @param hashtags    Список хештегов
     * @return ID опубликованного сообщения
     * @throws TelegramTokenExpiredException если токен истек
     * @throws RuntimeException              если публикация не удалась
     */
    public String postToTelegram(String credentials, String postText, List<String> hashtags) {
        try {
            // Парсим credentials
            TelegramCredentials creds = parseCredentials(credentials);

            // Формируем полный текст поста с контактами и хештегами
            String fullPostText = SocialPostTextFormatter.buildPostText(postText, hashtags, 4096);

            // Проверяем длину сообщения (Telegram limit: 4096 characters)
            if (fullPostText.length() > 4096) {
                logger.warn("Message too long ({} chars), truncating to 4096 characters", fullPostText.length());
                fullPostText = fullPostText.substring(0, 4093) + "...";
            }

            // Создаем WebClient с токеном в базовом URL
            WebClient webClient = createWebClient(creds.botToken);

            // Публикуем сообщение
            String messageId = sendMessage(webClient, creds.chatId, fullPostText);

            logger.info("Successfully posted to Telegram. Message ID: {}", messageId);
            return messageId;

        } catch (WebClientResponseException e) {
            if (isTokenExpiredError(e)) {
                throw parseTelegramError(e);
            }
            logger.error("Telegram API error: {} - {}", e.getStatusCode(), e.getResponseBodyAsString());
            throw new RuntimeException("Failed to post to Telegram: " + e.getMessage(), e);
        } catch (TelegramTokenExpiredException e) {
            throw e;
        } catch (Exception e) {
            logger.error("Unexpected error posting to Telegram", e);
            throw new RuntimeException("Failed to post to Telegram", e);
        }
    }

    /**
     * Публикует пост в Telegram с изображением через Bot API
     * 
     * @param credentials      JSON строка с botToken и chatId: {"botToken": "...", "chatId": "..."}
     * @param postText         Текст поста
     * @param hashtags         Список хештегов
     * @param imageData        Данные изображения
     * @param imageContentType MIME тип изображения (например, "image/png")
     * @return ID опубликованного сообщения
     * @throws TelegramTokenExpiredException если токен истек
     * @throws RuntimeException              если публикация не удалась
     */
    public String postToTelegramWithImage(String credentials, String postText, List<String> hashtags,
            byte[] imageData, String imageContentType) {
        try {
            // Парсим credentials
            TelegramCredentials creds = parseCredentials(credentials);

            // Формируем полный текст поста с контактами и хештегами
            String fullPostText = SocialPostTextFormatter.buildPostText(postText, hashtags, 1024);

            // Проверяем длину сообщения (Telegram limit: 1024 characters для caption)
            if (fullPostText.length() > 1024) {
                logger.warn("Caption too long ({} chars), truncating to 1024 characters", fullPostText.length());
                fullPostText = fullPostText.substring(0, 1021) + "...";
            }

            // Создаем WebClient с токеном в базовом URL
            WebClient webClient = createWebClient(creds.botToken);

            // Публикуем сообщение с изображением
            String messageId = sendPhoto(webClient, creds.chatId, fullPostText, imageData, imageContentType);

            logger.info("Successfully posted to Telegram with image. Message ID: {}", messageId);
            return messageId;

        } catch (WebClientResponseException e) {
            if (isTokenExpiredError(e)) {
                throw parseTelegramError(e);
            }
            logger.error("Telegram API error: {} - {}", e.getStatusCode(), e.getResponseBodyAsString());
            throw new RuntimeException("Failed to post to Telegram: " + e.getMessage(), e);
        } catch (TelegramTokenExpiredException e) {
            throw e;
        } catch (Exception e) {
            logger.error("Unexpected error posting to Telegram", e);
            throw new RuntimeException("Failed to post to Telegram", e);
        }
    }

    /**
     * Парсит credentials из JSON строки
     */
    private TelegramCredentials parseCredentials(String credentials) {
        try {
            JsonNode jsonNode = objectMapper.readTree(credentials);
            String botToken = jsonNode.has("botToken") ? jsonNode.get("botToken").asText() : null;
            String chatId = jsonNode.has("chatId") ? jsonNode.get("chatId").asText() : null;

            if (botToken == null || botToken.isEmpty()) {
                throw new IllegalArgumentException("botToken is required in credentials");
            }
            if (chatId == null || chatId.isEmpty()) {
                throw new IllegalArgumentException("chatId is required in credentials");
            }

            return new TelegramCredentials(botToken, chatId);
        } catch (JsonProcessingException e) {
            logger.error("Failed to parse Telegram credentials", e);
            throw new IllegalArgumentException("Invalid credentials format. Expected JSON: {\"botToken\": \"...\", \"chatId\": \"...\"}", e);
        }
    }

    /**
     * Создает WebClient с токеном в базовом URL
     */
    private WebClient createWebClient(String botToken) {
        HttpClient httpClient = HttpClient.create()
                .responseTimeout(Duration.ofMillis(timeoutMs));

        return WebClient.builder()
                .baseUrl(TELEGRAM_API_BASE + botToken)
                .clientConnector(new ReactorClientHttpConnector(httpClient))
                .build();
    }

    /**
     * Отправляет текстовое сообщение в Telegram
     */
    private String sendMessage(WebClient webClient, String chatId, String text) throws JsonProcessingException {
        try {
            MultiValueMap<String, String> formData = new LinkedMultiValueMap<>();
            formData.add("chat_id", chatId);
            formData.add("text", text);
            formData.add("parse_mode", "HTML"); // Используем HTML для форматирования

            String response = webClient.post()
                    .uri("/sendMessage")
                    .contentType(MediaType.APPLICATION_FORM_URLENCODED)
                    .body(BodyInserters.fromFormData(formData))
                    .retrieve()
                    .bodyToMono(String.class)
                    .block(Duration.ofMillis(timeoutMs));

            JsonNode jsonNode = objectMapper.readTree(response);
            if (!jsonNode.get("ok").asBoolean()) {
                String errorDescription = jsonNode.has("description") 
                    ? jsonNode.get("description").asText() 
                    : "Unknown error";
                throw new RuntimeException("Telegram API error: " + errorDescription);
            }

            JsonNode result = jsonNode.get("result");
            return String.valueOf(result.get("message_id").asLong());
        } catch (WebClientResponseException e) {
            if (isTokenExpiredError(e)) {
                throw parseTelegramError(e);
            }
            logger.error("Telegram API error sending message: {} - {}", e.getStatusCode(), e.getResponseBodyAsString());
            throw new RuntimeException("Failed to send message: " + e.getMessage(), e);
        }
    }

    /**
     * Отправляет сообщение с изображением в Telegram
     */
    private String sendPhoto(WebClient webClient, String chatId, String caption, byte[] imageData, String imageContentType)
            throws JsonProcessingException {
        try {
            // Создаем multipart form data для загрузки изображения
            ByteArrayResource imageResource = new ByteArrayResource(imageData) {
                @Override
                public String getFilename() {
                    return "image.jpg";
                }
            };

            MultiValueMap<String, Object> formData = new LinkedMultiValueMap<>();
            formData.add("chat_id", chatId);
            formData.add("photo", imageResource);
            if (caption != null && !caption.isEmpty()) {
                formData.add("caption", caption);
            }
            formData.add("parse_mode", "HTML"); // Используем HTML для форматирования

            String response = webClient.post()
                    .uri("/sendPhoto")
                    .contentType(MediaType.MULTIPART_FORM_DATA)
                    .body(BodyInserters.fromMultipartData(formData))
                    .retrieve()
                    .bodyToMono(String.class)
                    .block(Duration.ofMillis(timeoutMs * 2)); // Увеличиваем таймаут для загрузки изображения

            JsonNode jsonNode = objectMapper.readTree(response);
            if (!jsonNode.get("ok").asBoolean()) {
                String errorDescription = jsonNode.has("description") 
                    ? jsonNode.get("description").asText() 
                    : "Unknown error";
                throw new RuntimeException("Telegram API error: " + errorDescription);
            }

            JsonNode result = jsonNode.get("result");
            return String.valueOf(result.get("message_id").asLong());
        } catch (WebClientResponseException e) {
            if (isTokenExpiredError(e)) {
                throw parseTelegramError(e);
            }
            logger.error("Telegram API error sending photo: {} - {}", e.getStatusCode(), e.getResponseBodyAsString());
            throw new RuntimeException("Failed to send photo: " + e.getMessage(), e);
        }
    }

    /**
     * Проверяет, является ли ошибка истечением токена
     * Telegram возвращает HTTP 401 (Unauthorized) для недействительных токенов
     */
    private boolean isTokenExpiredError(WebClientResponseException e) {
        if (e.getStatusCode().value() == 401) {
            return true;
        }

        // Также проверяем ответ API на наличие ошибки "Unauthorized"
        try {
            String responseBody = e.getResponseBodyAsString();
            if (responseBody == null || responseBody.isEmpty()) {
                return false;
            }

            JsonNode errorNode = objectMapper.readTree(responseBody);
            if (errorNode.has("ok") && !errorNode.get("ok").asBoolean()) {
                String description = errorNode.has("description") 
                    ? errorNode.get("description").asText().toLowerCase() 
                    : "";
                // Проверяем на типичные сообщения об ошибке авторизации
                return description.contains("unauthorized") 
                    || description.contains("invalid token")
                    || description.contains("token is invalid");
            }
        } catch (Exception ex) {
            logger.warn("Failed to parse Telegram error response", ex);
        }

        return false;
    }

    /**
     * Парсит ошибку Telegram API и создает исключение для истекшего токена
     */
    private TelegramTokenExpiredException parseTelegramError(WebClientResponseException e) {
        try {
            String responseBody = e.getResponseBodyAsString();
            String errorMessage = "Bot token is invalid or expired";

            if (responseBody != null && !responseBody.isEmpty()) {
                try {
                    JsonNode errorNode = objectMapper.readTree(responseBody);
                    if (errorNode.has("description")) {
                        errorMessage = errorNode.get("description").asText();
                    }
                } catch (Exception ex) {
                    logger.warn("Failed to parse Telegram error response", ex);
                }
            }

            String userMessage = "Telegram bot token has expired or is invalid. Please update your credentials. " +
                    "Error: " + errorMessage;

            return new TelegramTokenExpiredException(userMessage, errorMessage, e.getStatusCode().value(), e);
        } catch (Exception ex) {
            logger.warn("Failed to parse Telegram error details", ex);
            return new TelegramTokenExpiredException(
                    "Telegram bot token has expired or is invalid. Please update your credentials.",
                    "Bot token expired or invalid",
                    401,
                    e);
        }
    }

    /**
     * Внутренний класс для хранения credentials
     */
    private static class TelegramCredentials {
        final String botToken;
        final String chatId;

        TelegramCredentials(String botToken, String chatId) {
            this.botToken = botToken;
            this.chatId = chatId;
        }
    }
}
