package kz.konturai.parser.service;

import kz.konturai.parser.dto.ReportGenerateRequest;
import kz.konturai.parser.model.MarketItem;
import kz.konturai.parser.repository.MarketItemRepository;
import kz.konturai.parser.model.ReportHistory;
import kz.konturai.parser.repository.ReportHistoryRepository;
import org.springframework.http.MediaType;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
import org.apache.poi.xwpf.usermodel.XWPFDocument;
import org.apache.poi.xwpf.usermodel.XWPFParagraph;
import org.apache.poi.xwpf.usermodel.XWPFRun;
import org.apache.poi.util.Units;
import com.lowagie.text.Document;
import com.lowagie.text.Element;
import com.lowagie.text.Image;
import com.lowagie.text.Paragraph;
import com.lowagie.text.pdf.PdfWriter;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.*;
import java.util.stream.Collectors;

@Service
public class ReportGenerationService {

    public static class ReportBinary {
        private final String filename;
        private final MediaType mediaType;
        private final byte[] bytes;

        public ReportBinary(String filename, MediaType mediaType, byte[] bytes) {
            this.filename = filename;
            this.mediaType = mediaType;
            this.bytes = bytes;
        }

        public String getFilename() {
            return filename;
        }

        public MediaType getMediaType() {
            return mediaType;
        }

        public byte[] getBytes() {
            return bytes;
        }
    }

    private record Sections(String annotation, String introduction, String mainNarrative, String recommendations) {
            private Sections(String annotation, String introduction, String mainNarrative, String recommendations) {
                this.annotation = annotation == null ? "" : annotation;
                this.introduction = introduction == null ? "" : introduction;
                this.mainNarrative = mainNarrative == null ? "" : mainNarrative;
                this.recommendations = recommendations == null ? "" : recommendations;
            }
        }

    private final MarketItemRepository marketItemRepository;
    private final OpenAIAnalyticsService openAIAnalyticsService;
    private final ReportHistoryRepository reportHistoryRepository;
    private final MinIOService minIOService;
    private final EmailService emailService;

    public ReportGenerationService(MarketItemRepository marketItemRepository,
            OpenAIAnalyticsService openAIAnalyticsService,
            ReportHistoryRepository reportHistoryRepository,
            MinIOService minIOService,
            EmailService emailService) {
        this.marketItemRepository = marketItemRepository;
        this.openAIAnalyticsService = openAIAnalyticsService;
        this.reportHistoryRepository = reportHistoryRepository;
        this.minIOService = minIOService;
        this.emailService = emailService;
    }

    public ReportBinary simpleGenerate(ReportGenerateRequest req) {
        LocalDateTime start = req.getStartDate();
        LocalDateTime end = req.getEndDate();

        // Устанавливаем значения по умолчанию, если даты не указаны
        if (start == null) {
            start = LocalDateTime.now().minusDays(30); // 30 дней назад
        }
        if (end == null) {
            end = LocalDateTime.now(); // сейчас
        }

        List<MarketItem> items = marketItemRepository
                .findByPublishedAtBetween(start, end, org.springframework.data.domain.Pageable.unpaged())
                .getContent();

        String combined = getCombinedSummaries(items);
        String contextBlock = String.format(
                "Заголовок отчёта: %s\nАвтор: %s\nКомпания: %s\nПериод: %s — %s\n",
                Optional.ofNullable(req.getReportTitle()).orElse("Отчёт"),
                Optional.ofNullable(req.getAuthorName()).orElse("Не указан"),
                Optional.ofNullable(req.getCompanyName()).orElse("Не указана"),
                start == null ? "?" : start.toString(),
                end == null ? "?" : end.toString());

        String instruction = "Ты — аналитик. На основе кратких сводок новостей ниже напиши цельный аналитический текст отчёта для руководства. "
                + "Структура (без нумерации разделов): Краткое содержание (2–3 абзаца), Введение (1 абзац: цель, источники, подход), "
                + "Основная часть (3–5 ключевых тенденций и событий периода с краткими пояснениями, допускаются маркированные списки), "
                + "Рекомендации (4–6 практических пунктов), Заключение (1 абзац). Стиль: деловой, нейтральный, без вводных вроде 'в данном тексте'. "
                + "Не упоминай, что ты ИИ. Используй русский язык.\n\nКонтекст отчёта:\n" + contextBlock
                + "\nСводки новостей:\n";

        String generated = openAIAnalyticsService.generateWithInstruction(combined, instruction);
        if (generated == null) {
            generated = "(Не удалось сгенерировать текст отчёта по предоставленным данным.)";
        }

        String filename = "report_simple_" + end.toLocalDate().toString() + "-"
                + System.currentTimeMillis() + ".txt";
        return new ReportBinary(filename, MediaType.TEXT_PLAIN,
                generated.getBytes(java.nio.charset.StandardCharsets.UTF_8));
    }

    @Async("reportGenerationExecutor")
    public void generateAsync(ReportGenerateRequest req) {
        try {
            generate(req);
            // Report is automatically saved to database, MinIO and sent by email in the
            // generate() method
        } catch (Exception e) {
            // Log error but don't throw to avoid async method issues
            System.err.println("Error generating report asynchronously: " + e.getMessage());
        }
    }

    public ReportBinary generate(ReportGenerateRequest req) {
        LocalDateTime start = req.getStartDate();
        LocalDateTime end = req.getEndDate();

        // Устанавливаем значения по умолчанию, если даты не указаны
        if (start == null) {
            start = LocalDateTime.now().minusDays(30); // 30 дней назад
        }
        if (end == null) {
            end = LocalDateTime.now(); // сейчас
        }

        List<MarketItem> items = marketItemRepository
                .findByPublishedAtBetween(start, end, org.springframework.data.domain.Pageable.unpaged())
                .getContent();

        Sections sections = buildSections(req, items);

        ReportBinary rb;
        String dateSuffix = end.toLocalDate().toString();
        if ("DOCX".equalsIgnoreCase(req.getFormat())) {
            byte[] docx = buildDocx(req, items, sections);
            String filename = "report_" + dateSuffix + "-" + System.currentTimeMillis() + ".docx";
            rb = new ReportBinary(filename,
                    MediaType.parseMediaType("application/vnd.openxmlformats-officedocument.wordprocessingml.document"),
                    docx);
        } else {
            byte[] pdf = buildPdf(req, items, sections);
            String filename = "report_" + dateSuffix + "-" + System.currentTimeMillis() + ".pdf";
            rb = new ReportBinary(filename, MediaType.APPLICATION_PDF, pdf);
        }
        persistReport(req, rb);

        // Отправляем PDF на email, если указан email и формат PDF
        // if (req.getRecipientEmail() != null &&
        // !req.getRecipientEmail().trim().isEmpty()
        // && "PDF".equalsIgnoreCase(req.getFormat())) {
        // try {
        // emailService.sendReportByEmail(
        // req.getRecipientEmail(),
        // req.getReportTitle(),
        // req.getAuthorName(),
        // req.getCompanyName(),
        // rb.getBytes(),
        // rb.getFilename());
        // } catch (Exception e) {
        // System.err.println("Ошибка при отправке email: " + e.getMessage());
        // // Не прерываем выполнение, если email не отправился
        // }
        // }

        return rb;
    }

    private void persistReport(ReportGenerateRequest req, ReportBinary rb) {
        try {
            if (rb.getBytes() != null) {
                // Upload to MinIO
                minIOService.uploadFile(rb.getFilename(), rb.getBytes(), rb.getMediaType().toString());
            }

            ReportHistory rh = new ReportHistory();
            rh.setReportTitle(req.getReportTitle());
            rh.setAuthorName(req.getAuthorName());
            rh.setCompanyName(req.getCompanyName());
            rh.setStartDate(req.getStartDate());
            rh.setEndDate(req.getEndDate());
            rh.setFormat(req.getFormat());
            rh.setFilename(rb.getFilename());
            rh.setFilePath(rb.getFilename()); // Store object name instead of file path
            rh.setFileSize(rb.getBytes() == null ? 0 : rb.getBytes().length);
            rh.setCreatedAt(java.time.LocalDateTime.now());
            reportHistoryRepository.save(rh);
        } catch (Exception ignored) {
        }
    }

    private String getCombinedSummaries(List<MarketItem> items) {
        String combined = items.stream()
                .map(i -> i.getAnalytics() != null ? i.getAnalytics().getSummary() : null)
                .filter(Objects::nonNull)
                .collect(Collectors.joining("\n\n"));
        if (combined.length() > 8000) {
            combined = combined.substring(0, 8000);
        }
        return combined;
    }

    private String buildAnnotation(List<MarketItem> items) {
        String combined = getCombinedSummaries(items);
        if (combined.isBlank()) {
            return "";
        }
        String instruction = "На основе этих кратких сводок новостей напиши общую аннотацию на 2-3 абзаца, выделяя ключевые тренды и события.";
        String res = openAIAnalyticsService.generateWithInstruction(combined, instruction);
        return res == null ? "" : res;
    }

    private Sections buildSections(ReportGenerateRequest req, List<MarketItem> items) {
        String combined = getCombinedSummaries(items);
        if (combined.isBlank()) {
            return new Sections("", "", "", "");
        }
        String annotation = openAIAnalyticsService.generateWithInstruction(combined,
                "На основе кратких сводок новостей напиши краткое содержание (2-3 абзаца), выделив ключевые тренды и события. Стиль: деловой, нейтральный.");

        String intro = openAIAnalyticsService.generateWithInstruction(combined,
                "Напиши краткое введение к аналитическому отчёту за период. Укажи цель отчёта, источники данных и подход к анализу. Объём: 1 абзац.");

        String mainPart = openAIAnalyticsService.generateWithInstruction(combined,
                "Сформируй основную часть отчёта: перечисли и кратко объясни 3-5 ключевых тенденций и событий периода, добавь замеченные риски и возможности. Используй маркированные пункты и короткие абзацы.");

        String recs = openAIAnalyticsService.generateWithInstruction(combined,
                "На основе сводок сформулируй практические рекомендации (4-6 пунктов) для бизнеса/маркетинга. Формат: маркированный список, краткие и конкретные формулировки.");

        return new Sections(annotation, intro, mainPart, recs);
    }

    private byte[] buildDocx(ReportGenerateRequest req, List<MarketItem> items, Sections sections) {
        try (XWPFDocument doc = new XWPFDocument(); ByteArrayOutputStream out = new ByteArrayOutputStream()) {
            DateTimeFormatter dt = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm");

            // Title page
            addHeading(doc, req.getReportTitle());
            addParagraph(doc, "Автор: " + req.getAuthorName());
            addParagraph(doc, "Компания: " + req.getCompanyName());
            addParagraph(doc, "Дата: " + LocalDateTime.now().format(dt));

            // TOC placeholder
            addHeading(doc, "Содержание");
            addParagraph(doc,
                    "(Автоматически) Аннотация, Введение, Основная часть, Рекомендации, Список литературы, Приложения");

            // Annotation
            addHeading(doc, "Краткое содержание");
            addParagraph(doc, sections.annotation().isBlank() ? "(недоступно)" : sections.annotation());

            // Intro
            addHeading(doc, "Введение");
            addParagraph(doc, sections.introduction().isBlank()
                    ? String.format(
                            "Целью данного отчёта является анализ новостного фона в сфере маркетинга и бизнеса за период с %s по %s. Были проанализированы публикации из ключевых источников для выявления основных трендов.",
                            req.getStartDate() != null ? req.getStartDate().format(dt) : "не указано",
                            req.getEndDate() != null ? req.getEndDate().format(dt) : "не указано")
                    : sections.introduction());

            // Main
            addHeading(doc, "Основная часть");
            addParagraph(doc, sections.mainNarrative());
            addSubheading(doc, "Ключевые публикации");
            List<MarketItem> top = items.stream()
                    .sorted(Comparator
                            .comparing(MarketItem::getPublishedAt, Comparator.nullsLast(Comparator.naturalOrder()))
                            .reversed())
                    .limit(10)
                    .toList();
            for (MarketItem mi : top) {
                addParagraph(doc, String.format("— %s | %s | %s", safe(mi.getTitle()), safe(mi.getSourceName()),
                        mi.getPublishedAt() == null ? "" : mi.getPublishedAt().format(dt)));
                if (mi.getAnalytics() != null && mi.getAnalytics().getSummary() != null) {
                    addParagraph(doc, mi.getAnalytics().getSummary());
                }
            }

            addSubheading(doc, "Анализ тональности");
            Map<String, Long> sentiment = countSentiment(items);
            byte[] piePng = buildSentimentChart(sentiment);
            if (piePng != null) {
                XWPFParagraph p = doc.createParagraph();
                XWPFRun r = p.createRun();
                r.addPicture(new ByteArrayInputStream(piePng), XWPFDocument.PICTURE_TYPE_PNG, "sentiment.png",
                        Units.toEMU(480), Units.toEMU(320));
            }

            addSubheading(doc, "Упоминаемые компании и персоны");
            Map<String, Long> companies = collectEntityFreq(items, "companies");
            Map<String, Long> persons = collectEntityFreq(items, "persons");
            addParagraph(doc, "Компании: " + formatTop(companies));
            addParagraph(doc, "Персоны: " + formatTop(persons));

            // Recommendations
            addHeading(doc, "Рекомендации");
            addParagraph(doc, sections.recommendations().isBlank()
                    ? "Рассмотрите возможность усиления присутствия в медиа по ключевым темам отчёта."
                    : sections.recommendations());

            // Bibliography
            addHeading(doc, "Список литературы");
            for (MarketItem mi : items) {
                addParagraph(doc, String.format("%s. Источник: %s. URL: %s",
                        safe(mi.getTitle()), safe(mi.getSourceName()), safe(mi.getUrl())));
            }

            // Appendix
            addHeading(doc, "Приложения");
            addParagraph(doc, "Полная таблица статей доступна в базе данных за период отчёта.");

            doc.write(out);
            return out.toByteArray();
        } catch (Exception e) {
            return new byte[0];
        }
    }

    private byte[] buildPdf(ReportGenerateRequest req, List<MarketItem> items, Sections sections) {
        try (ByteArrayOutputStream out = new ByteArrayOutputStream()) {
            Document pdf = new Document();
            PdfWriter.getInstance(pdf, out);
            pdf.open();
            DateTimeFormatter dt = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm");

            pdf.add(new Paragraph(req.getReportTitle()));
            pdf.add(new Paragraph("Автор: " + req.getAuthorName()));
            pdf.add(new Paragraph("Компания: " + req.getCompanyName()));
            pdf.add(new Paragraph("Дата: " + LocalDateTime.now().format(dt)));

            pdf.add(new Paragraph(
                    "\nСодержание: Аннотация, Введение, Основная часть, Рекомендации, Список литературы, Приложения\n"));

            pdf.add(new Paragraph("Краткое содержание"));
            pdf.add(new Paragraph(sections.annotation().isBlank() ? "(недоступно)" : sections.annotation()));

            pdf.add(new Paragraph("Введение"));
            pdf.add(new Paragraph(sections.introduction().isBlank()
                    ? String.format(
                            "Целью данного отчёта является анализ новостного фона в сфере маркетинга и бизнеса за период с %s по %s. Были проанализированы публикации из ключевых источников для выявления основных трендов.",
                            req.getStartDate() != null ? req.getStartDate().format(dt) : "не указано",
                            req.getEndDate() != null ? req.getEndDate().format(dt) : "не указано")
                    : sections.introduction()));

            pdf.add(new Paragraph("Основная часть"));
            pdf.add(new Paragraph(sections.mainNarrative()));
            pdf.add(new Paragraph("Ключевые публикации"));
            List<MarketItem> top = items.stream()
                    .sorted(Comparator
                            .comparing(MarketItem::getPublishedAt, Comparator.nullsLast(Comparator.naturalOrder()))
                            .reversed())
                    .limit(10)
                    .toList();
            for (MarketItem mi : top) {
                pdf.add(new Paragraph(String.format("— %s | %s | %s", safe(mi.getTitle()), safe(mi.getSourceName()),
                        mi.getPublishedAt() == null ? "" : mi.getPublishedAt().format(dt))));
                if (mi.getAnalytics() != null && mi.getAnalytics().getSummary() != null) {
                    pdf.add(new Paragraph(mi.getAnalytics().getSummary()));
                }
            }

            pdf.add(new Paragraph("Анализ тональности"));
            Map<String, Long> sentiment = countSentiment(items);
            byte[] piePng = buildSentimentChart(sentiment);
            if (piePng != null) {
                Image img = Image.getInstance(piePng);

                // Улучшенное масштабирование для лучшего качества
                float availableWidth = 480f;
                float availableHeight = 320f;

                // Рассчитываем оптимальный размер с сохранением пропорций
                float scaleX = availableWidth / img.getWidth();
                float scaleY = availableHeight / img.getHeight();
                float scale = Math.min(scaleX, scaleY);

                // Ограничиваем масштабирование для читаемости
                scale = Math.min(scale, 1.0f);
                scale = Math.max(scale, 0.5f);

                img.scalePercent(scale * 100);
                img.setAlignment(Element.ALIGN_CENTER);
                img.setSpacingBefore(10);
                img.setSpacingAfter(10);

                pdf.add(img);
            }

            Map<String, Long> companies = collectEntityFreq(items, "companies");
            Map<String, Long> persons = collectEntityFreq(items, "persons");
            pdf.add(new Paragraph("Упоминаемые компании и персоны"));
            pdf.add(new Paragraph("Компании: " + formatTop(companies)));
            pdf.add(new Paragraph("Персоны: " + formatTop(persons)));

            pdf.add(new Paragraph("Рекомендации"));
            pdf.add(new Paragraph(sections.recommendations().isBlank()
                    ? "Рассмотрите возможность усиления присутствия в медиа по ключевым темам отчёта."
                    : sections.recommendations()));

            pdf.add(new Paragraph("Список литературы"));
            for (MarketItem mi : items) {
                pdf.add(new Paragraph(String.format("%s. Источник: %s. URL: %s",
                        safe(mi.getTitle()), safe(mi.getSourceName()), safe(mi.getUrl()))));
            }

            pdf.add(new Paragraph("Приложения"));
            pdf.add(new Paragraph("Полная таблица статей доступна в базе данных за период отчёта."));

            pdf.close();
            return out.toByteArray();
        } catch (Exception e) {
            return new byte[0];
        }
    }

    private void addHeading(XWPFDocument doc, String text) {
        XWPFParagraph p = doc.createParagraph();
        XWPFRun r = p.createRun();
        r.setBold(true);
        r.setFontSize(16);
        r.setText(text);
    }

    private void addSubheading(XWPFDocument doc, String text) {
        XWPFParagraph p = doc.createParagraph();
        XWPFRun r = p.createRun();
        r.setBold(true);
        r.setFontSize(13);
        r.setText(text);
    }

    private void addParagraph(XWPFDocument doc, String text) {
        XWPFParagraph p = doc.createParagraph();
        XWPFRun r = p.createRun();
        r.setText(Optional.ofNullable(text).orElse(""));
    }

    private Map<String, Long> countSentiment(List<MarketItem> items) {
        Map<String, Long> map = new LinkedHashMap<>();
        map.put("positive", 0L);
        map.put("neutral", 0L);
        map.put("negative", 0L);
        for (MarketItem mi : items) {
            String s = mi.getAnalytics() == null ? null : mi.getAnalytics().getSentiment();
            if (s == null)
                continue;
            s = s.trim().toLowerCase();
            if (!map.containsKey(s))
                continue;
            map.put(s, map.get(s) + 1);
        }
        return map;
    }

    private byte[] buildSentimentChart(Map<String, Long> sentiment) {
        // Disable chart generation in Docker to avoid font library issues
        // Return null to skip chart insertion
        return null;
    }

    private Map<String, Long> collectEntityFreq(List<MarketItem> items, String key) {
        Map<String, Long> freq = new HashMap<>();
        for (MarketItem mi : items) {
            Map<String, Object> entities = mi.getAnalytics() == null ? null : mi.getAnalytics().getEntities();
            if (entities == null)
                continue;
            Object v = entities.get(key);
            if (v instanceof Collection<?> col) {
                for (Object o : col) {
                    String name = String.valueOf(o);
                    if (name == null || name.isBlank())
                        continue;
                    freq.put(name, freq.getOrDefault(name, 0L) + 1);
                }
            }
        }
        return freq.entrySet().stream()
                .sorted(Map.Entry.<String, Long>comparingByValue().reversed())
                .limit(10)
                .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue, (a, b) -> a, LinkedHashMap::new));
    }

    private String formatTop(Map<String, Long> m) {
        return m.entrySet().stream()
                .map(e -> e.getKey() + " (" + e.getValue() + ")")
                .collect(Collectors.joining(", "));
    }

    private String safe(String s) {
        return s == null ? "" : s;
    }
}
