package kz.konturai.parser.service;

import kz.konturai.parser.dto.FinalReportPayload;
import kz.konturai.parser.dto.ChartData;
import kz.konturai.parser.util.SvgToPngConverter;
import org.springframework.stereotype.Service;
import reactor.core.publisher.Mono;
import reactor.core.scheduler.Schedulers;

import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;

import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;

@Service
public class ReportSynthesisService {

    private final OpenAIAnalyticsService openAIAnalyticsService;
    private final OpenAiChartService openAiChartService;
    private final ObjectMapper objectMapper = new ObjectMapper();

    public ReportSynthesisService(OpenAIAnalyticsService openAIAnalyticsService,
            OpenAiChartService openAiChartService) {
        this.openAIAnalyticsService = openAIAnalyticsService;
        this.openAiChartService = openAiChartService;
    }

    public Mono<String> synthesizeReport(String originalQuery, List<String> learnings, String lang) {
        String aggregatedLearnings = formatLearnings(learnings);
        String prompt = buildTextPrompt(originalQuery, aggregatedLearnings, lang);
        return Mono
                .fromCallable(() -> openAIAnalyticsService.generateWithInstruction(aggregatedLearnings, prompt, lang));
    }

    public Mono<FinalReportPayload> synthesizeReportAndCharts(String originalQuery, List<String> learnings,
            String lang) {
        String aggregatedLearnings = formatLearnings(learnings);

        Mono<String> textMono = Mono.fromCallable(
                () -> openAIAnalyticsService.generateWithInstruction(aggregatedLearnings,
                        buildTextPrompt(originalQuery, aggregatedLearnings, lang), lang))
                .subscribeOn(Schedulers.boundedElastic());

        Mono<List<byte[]>> chartsMono = openAiChartService.getChartDataJson(aggregatedLearnings, lang)
                .flatMap(jsonArrayString -> {
                    String cleanJsonArray = extractJsonArray(jsonArrayString);
                    if (cleanJsonArray == null || cleanJsonArray.isEmpty() || cleanJsonArray.equals("[]")) {
                        return reactor.core.publisher.Mono.just(new ArrayList<byte[]>());
                    }
                    try {
                        List<ChartData> chartDataList = objectMapper.readValue(cleanJsonArray,
                                new TypeReference<List<ChartData>>() {
                                });
                        return reactor.core.publisher.Flux.fromIterable(chartDataList)
                                .concatMap(chartData -> {
                                    try {
                                        String chartDataJson = objectMapper.writeValueAsString(chartData);
                                        return openAiChartService.getChartSvg(chartDataJson, lang)
                                                .map(this::extractSvgCode)
                                                .map(SvgToPngConverter::convert)
                                                .filter(Objects::nonNull);
                                    } catch (Exception e) {
                                        return reactor.core.publisher.Mono.empty();
                                    }
                                })
                                .collectList();
                    } catch (Exception e) {
                        return reactor.core.publisher.Mono.just(new ArrayList<byte[]>());
                    }
                })
                .subscribeOn(Schedulers.boundedElastic());

        return Mono.zip(textMono, chartsMono)
                .map(tuple -> new FinalReportPayload(tuple.getT1(), tuple.getT2()));
    }

    private String formatLearnings(List<String> learnings) {
        if (learnings == null || learnings.isEmpty()) {
            return "- (данные не были предоставлены)";
        }
        List<String> cleaned = learnings.stream()
                .filter(Objects::nonNull)
                .map(String::trim)
                .filter(s -> !s.isEmpty())
                .collect(Collectors.toList());
        if (cleaned.isEmpty()) {
            return "- (данные не были предоставлены)";
        }
        return cleaned.stream().map(s -> "- " + s).collect(Collectors.joining("\n"));
    }

    private String buildTextPrompt(String originalQuery, String learningsAsString, String lang) {
        return "You are a professional AI report writer. Your task is to write a detailed and structured report based on the provided data."
                +
                "\n\n" +
                "CRITICAL INSTRUCTION: Your entire response must be ONLY the final report in Markdown format. Start directly with the main title. Do NOT include any conversational text, introductions, summaries of your instructions, or any questions at the end. Your output should be pure Markdown."
                +
                "\n\n" +
                "### Research Topic:\n" +
                originalQuery + "\n\n" +
                "### Collected Data (Learnings):\n" +
                learningsAsString + "\n\n" +
                "### Report Requirements:\n" +
                "1. **Language:** " + (lang == null ? "English" : lang) + ".\n" +
                "2. **Structure:** The report must have an introduction, a main body with sections (using `##`) and subsections (using `###`), and a conclusion.\n"
                +
                "3. **Style:** Transform the disjointed learnings into a cohesive, well-written article.\n" +
                "4. **Constraint:** Use ONLY the information from the 'Collected Data'.\n\n" +
                "Begin your response immediately with the main title using '#'.";
    }

    private String extractJsonArray(String ollamaResponse) {
        if (ollamaResponse == null)
            return null;
        int startIndex = ollamaResponse.indexOf("[");
        int endIndex = ollamaResponse.lastIndexOf("]");
        if (startIndex != -1 && endIndex != -1 && endIndex > startIndex) {
            return ollamaResponse.substring(startIndex, endIndex + 1);
        }
        return null;
    }

    private String extractSvgCode(String ollamaResponse) {
        if (ollamaResponse == null || ollamaResponse.isBlank()) {
            return null;
        }
        int startXml = ollamaResponse.indexOf("```xml");
        if (startXml != -1) {
            int startSvg = ollamaResponse.indexOf("<svg", startXml);
            int endSvg = ollamaResponse.lastIndexOf("</svg>");
            if (startSvg != -1 && endSvg != -1 && endSvg > startSvg) {
                return ollamaResponse.substring(startSvg, endSvg + 6);
            }
        }
        int startSvg = ollamaResponse.indexOf("<svg");
        int endSvg = ollamaResponse.lastIndexOf("</svg>");
        if (startSvg != -1 && endSvg != -1 && endSvg > startSvg) {
            return ollamaResponse.substring(startSvg, endSvg + 6);
        }
        return null;
    }
}
