package kz.konturai.parser.service;

import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import kz.konturai.parser.exception.ClaudeApiException;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Service;
import org.springframework.web.reactive.function.client.WebClient;
import reactor.util.retry.Retry;

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

/**
 * Сервис для вызовов Anthropic Claude API.
 * Retry: 3 попытки с exponential backoff (1s, 2s, 4s).
 * Timeout: 60 секунд.
 */
@Service
@Slf4j
public class ClaudeApiService {

    private static final String CLAUDE_MODEL = "claude-sonnet-4-5";
    private static final int MAX_RETRIES = 2;
    private static final Duration TIMEOUT = Duration.ofSeconds(90);

    private final WebClient webClient;
    private final ObjectMapper objectMapper;
    private final String apiKey;

    public ClaudeApiService(
            @Value("${claude.api.key:}") String apiKey,
            ObjectMapper objectMapper) {
        this.apiKey = apiKey;
        this.objectMapper = objectMapper;
        this.webClient = WebClient.builder()
                .baseUrl("https://api.anthropic.com")
                .defaultHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE)
                .defaultHeader("x-api-key", apiKey)
                .defaultHeader("anthropic-version", "2023-06-01")
                .build();
    }

    /**
     * Отправляет запрос к Claude API и возвращает текст ответа.
     *
     * @param systemPrompt системный промпт
     * @param userMessage  пользовательское сообщение
     * @param maxTokens    максимальное количество токенов
     * @return текст ответа
     */
    public String call(String systemPrompt, String userMessage, int maxTokens) {
        if (apiKey == null || apiKey.isBlank()) {
            throw new ClaudeApiException("Claude API key не настроен. Установите claude.api.key в application.properties");
        }

        String preview = userMessage.length() > 200 ? userMessage.substring(0, 200) + "..." : userMessage;
        log.info("[Claude API] Запрос отправлен. Длина промпта: {} симв. Превью: {}", userMessage.length(), preview);

        long startTime = System.currentTimeMillis();

        try {
            Map<String, Object> body = Map.of(
                    "model", CLAUDE_MODEL,
                    "max_tokens", maxTokens,
                    "system", systemPrompt,
                    "messages", List.of(Map.of("role", "user", "content", userMessage))
            );

            String response = webClient.post()
                    .uri("/v1/messages")
                    .bodyValue(body)
                    .retrieve()
                    .bodyToMono(String.class)
                    .timeout(TIMEOUT)
                    .retryWhen(Retry.backoff(MAX_RETRIES, Duration.ofSeconds(1))
                            .maxBackoff(Duration.ofSeconds(4))
                            .doBeforeRetry(signal ->
                                    log.warn("[Claude API] Повторная попытка #{}", signal.totalRetries() + 1)))
                    .block();

            long elapsed = System.currentTimeMillis() - startTime;

            JsonNode root = objectMapper.readTree(response);
            String text = root.path("content").get(0).path("text").asText();

            log.info("[Claude API] Успешный ответ за {} мс. Длина ответа: {} симв.", elapsed, text.length());
            return text;

        } catch (ClaudeApiException e) {
            throw e;
        } catch (Exception e) {
            long elapsed = System.currentTimeMillis() - startTime;
            log.error("[Claude API] Ошибка за {} мс: {}", elapsed, e.getMessage());
            throw new ClaudeApiException("Ошибка вызова Claude API: " + e.getMessage(), e);
        }
    }

    /**
     * Парсит JSON-ответ от Claude в объект указанного класса.
     */
    public <T> T callAndParse(String systemPrompt, String userMessage, int maxTokens, Class<T> clazz) {
        String raw = call(systemPrompt, userMessage, maxTokens);
        try {
            // Claude может обернуть JSON в ```json ... ```
            String json = extractJson(raw);
            return objectMapper.readValue(json, clazz);
        } catch (Exception e) {
            log.error("[Claude API] Ошибка парсинга ответа в {}: {}", clazz.getSimpleName(), e.getMessage());
            throw new ClaudeApiException("Ошибка парсинга ответа Claude: " + e.getMessage(), e);
        }
    }

    private String extractJson(String raw) {
        if (raw == null) return "{}";
        String trimmed = raw.trim();
        // Убираем markdown ```json ... ```
        if (trimmed.startsWith("```")) {
            int start = trimmed.indexOf('\n');
            int end = trimmed.lastIndexOf("```");
            if (start >= 0 && end > start) {
                return trimmed.substring(start + 1, end).trim();
            }
        }
        return trimmed;
    }
}
