package kz.konturai.parser.service;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.ParameterizedTypeReference;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Service;
import org.springframework.web.reactive.function.client.WebClient;
import org.springframework.web.reactive.function.client.WebClientResponseException;
import org.springframework.web.reactive.function.client.ExchangeStrategies;
import reactor.core.publisher.Mono;
import reactor.netty.http.client.HttpClient;
import org.springframework.http.client.reactive.ReactorClientHttpConnector;
import reactor.util.retry.Retry;
import reactor.util.retry.RetryBackoffSpec;

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

@Service
public class OpenAIImageGenerationService implements ImageGenerationService {

    private static final Logger logger = LoggerFactory.getLogger(OpenAIImageGenerationService.class);
    private static final String DALL_E_API_URL = "https://api.openai.com/v1/images/generations";

    private final WebClient webClient;

    @Value("${openai.api.key}")
    private String apiKey;

    @Value("${openai.image.model:dall-e-3}")
    private String model;

    @Value("${openai.image.size:1024x1024}")
    private String imageSize;

    @Value("${openai.image.quality:standard}")
    private String imageQuality;

    @Value("${openai.image.style:natural}")
    private String imageStyle;

    @Value("${openai.timeoutMs:90000}")
    private long timeoutMs;

    @Value("${openai.retry.maxAttempts:3}")
    private int maxRetryAttempts;

    @Value("${openai.retry.initialDelayMs:2000}")
    private long initialRetryDelayMs;

    @Value("${openai.retry.maxDelayMs:60000}")
    private long maxRetryDelayMs;

    @Value("${openai.retry.multiplier:2.0}")
    private double retryMultiplier;

    public OpenAIImageGenerationService() {
        HttpClient httpClient = HttpClient.create()
                .responseTimeout(Duration.ofMillis(90000));

        // Configure exchange strategies to increase buffer limit for large base64
        // responses
        ExchangeStrategies strategies = ExchangeStrategies.builder()
                .codecs(configurer -> configurer.defaultCodecs().maxInMemorySize(10 * 1024 * 1024)) // 10 MB
                .build();

        this.webClient = WebClient.builder()
                .baseUrl(DALL_E_API_URL)
                .clientConnector(new ReactorClientHttpConnector(httpClient))
                .exchangeStrategies(strategies)
                .build();
    }

    /**
     * Генерирует изображение через OpenAI DALL-E API
     *
     * @param prompt Промпт для генерации изображения
     * @return Массив байтов изображения в формате PNG
     */
    public byte[] generateImage(String prompt) {
        return generateImage(prompt, imageSize);
    }

    @Override
    public byte[] generateImageWithReference(String prompt, byte[] referenceImageBytes) {
        return new byte[0];
    }

    /**
     * Генерирует изображение через OpenAI DALL-E API с указанным размером
     *
     * @param prompt Промпт для генерации изображения
     * @param size   Размер изображения (1024x1024, 1792x1024, 1024x1792)
     * @return Массив байтов изображения в формате PNG
     */
    public byte[] generateImage(String prompt, String size) {
        if (apiKey == null || apiKey.trim().isEmpty()) {
            logger.error("OpenAI API key is not configured. Cannot generate image.");
            return null;
        }

        if (prompt == null || prompt.trim().isEmpty()) {
            logger.warn("Empty prompt provided for image generation");
            return null;
        }

        try {
            // Обогащаем промпт для лучшего качества генерации
            String enrichedPrompt = enrichPromptForDallE(prompt);

            Map<String, Object> requestBody = new HashMap<>();
            requestBody.put("model", model);
            requestBody.put("prompt", enrichedPrompt);
            requestBody.put("n", 1);
            requestBody.put("size", size);
            requestBody.put("quality", "hd");
            requestBody.put("style", imageStyle);
            requestBody.put("response_format", "b64_json"); // Получаем изображение в base64

            logger.info("Requesting image generation from DALL-E with prompt: {}",
                    enrichedPrompt.substring(0, Math.min(100, enrichedPrompt.length())));

            Map<String, Object> response = webClient.post()
                    .header("Authorization", "Bearer " + apiKey)
                    .contentType(MediaType.APPLICATION_JSON)
                    .accept(MediaType.APPLICATION_JSON)
                    .bodyValue(requestBody)
                    .retrieve()
                    .bodyToMono(new ParameterizedTypeReference<Map<String, Object>>() {
                    })
                    .retryWhen(createRetrySpec("generateImage"))
                    .onErrorResume(err -> {
                        logger.error("DALL-E API request failed after retries: {} - {}", err.getMessage(),
                                err.getClass().getSimpleName());
                        if (err instanceof WebClientResponseException) {
                            WebClientResponseException wcre = (WebClientResponseException) err;
                            if (wcre.getStatusCode().value() == 401) {
                                logger.error(
                                        "OpenAI API key is invalid or expired. Please check your openai.api.key configuration.");
                            } else if (wcre.getStatusCode().value() == 429) {
                                logger.error("OpenAI API rate limit exceeded after all retry attempts.");
                            } else if (wcre.getStatusCode().value() >= 500) {
                                logger.error("OpenAI API server error after all retry attempts.");
                            }
                        }
                        return Mono.empty();
                    })
                    .block(Duration.ofMillis(timeoutMs));

            if (response == null) {
                logger.error("Failed to generate image: empty response from DALL-E API");
                return null;
            }

            // Извлекаем изображение из ответа
            @SuppressWarnings("unchecked")
            List<Map<String, Object>> data = (List<Map<String, Object>>) response.get("data");
            if (data == null || data.isEmpty()) {
                logger.error("No image data in DALL-E API response");
                return null;
            }

            Map<String, Object> imageData = data.get(0);
            String b64Json = (String) imageData.get("b64_json");
            if (b64Json == null) {
                logger.error("No b64_json in DALL-E API response");
                return null;
            }

            // Декодируем base64 в массив байтов
            byte[] imageBytes = java.util.Base64.getDecoder().decode(b64Json);
            logger.info("Successfully generated image. Size: {} bytes", imageBytes.length);
            return imageBytes;

        } catch (Exception e) {
            logger.error("Error generating image with DALL-E: {}", e.getMessage(), e);
            return null;
        }
    }

    /**
     * Обогащает промпт для DALL-E, добавляя инструкции для корректной генерации
     * изображений
     * и правильного отображения русского текста
     *
     * @param originalPrompt Исходный промпт от пользователя
     * @return Обогащенный промпт с инструкциями для DALL-E
     */
    private String enrichPromptForDallE(String originalPrompt) {
        // Проверка на пустую строку
        if (originalPrompt == null || originalPrompt.trim().isEmpty()) {
            return originalPrompt;
        }

        // Базовый шаблон обертки
        String baseWrapper = "I will provide a description of an image. Generate the image strictly following this description without adding extra objects or changing the artistic style unless specified. ";

        // Проверка на текст в кавычках (русский текст)
        boolean hasQuotedText = originalPrompt.contains("\"") || originalPrompt.contains("«")
                || originalPrompt.contains("»");
        if (hasQuotedText) {
            baseWrapper += "If the description contains text in quotes, write that text clearly and accurately in Russian. The text in quotes must be written exactly as provided, using the Russian alphabet (Cyrillic), with correct grammar and spelling. ";
        }

        // Проверка на запрос фото и добавление профессиональных ключевых слов
        String lowerPrompt = originalPrompt.toLowerCase();
        if (lowerPrompt.contains("фото") || lowerPrompt.contains("фотография") || lowerPrompt.contains("photo")) {
            baseWrapper += "Use natural lighting, high resolution, editorial style. ";
        }

        return baseWrapper + "Description: " + originalPrompt;
    }

    private RetryBackoffSpec createRetrySpec(String operation) {
        return Retry.backoff(maxRetryAttempts, Duration.ofMillis(initialRetryDelayMs))
                .maxBackoff(Duration.ofMillis(maxRetryDelayMs))
                .multiplier(retryMultiplier)
                .filter(throwable -> {
                    if (throwable instanceof WebClientResponseException) {
                        WebClientResponseException wcre = (WebClientResponseException) throwable;
                        int statusCode = wcre.getStatusCode().value();
                        // Only retry on 429 (rate limit) and 5xx (server errors)
                        return statusCode == 429 || statusCode >= 500;
                    }
                    // Retry on network errors
                    return throwable instanceof java.util.concurrent.TimeoutException
                            || throwable instanceof java.net.ConnectException
                            || throwable instanceof java.io.IOException;
                })
                .doBeforeRetry(retrySignal -> {
                    long attempt = retrySignal.totalRetries() + 1;
                    Throwable failure = retrySignal.failure();

                    Duration retryAfter = null;
                    if (failure instanceof WebClientResponseException) {
                        WebClientResponseException wcre = (WebClientResponseException) failure;
                        if (wcre.getStatusCode().value() == 429) {
                            String retryAfterHeader = wcre.getHeaders().getFirst("Retry-After");
                            if (retryAfterHeader != null) {
                                try {
                                    int seconds = Integer.parseInt(retryAfterHeader);
                                    retryAfter = Duration.ofSeconds(seconds);
                                    logger.warn(
                                            "DALL-E API returned 429 for operation '{}'. Retry-After: {} seconds. Will retry in {}ms (attempt {}/{})",
                                            operation, seconds, retryAfter.toMillis(), attempt, maxRetryAttempts);
                                } catch (NumberFormatException e) {
                                    // Ignore if header is not a number
                                }
                            }
                        }
                    }

                    if (retryAfter == null) {
                        logger.warn("DALL-E API returned error for operation '{}'. Will retry (attempt {}/{})",
                                operation, attempt, maxRetryAttempts);
                    }
                })
                .onRetryExhaustedThrow((retryBackoffSpec, retrySignal) -> {
                    logger.error("DALL-E request for operation '{}' exhausted all {} retry attempts. Giving up.",
                            operation, maxRetryAttempts);
                    return retrySignal.failure();
                });
    }
}
