package com.loremind.application.generationcontext;

import com.loremind.domain.generationcontext.ChatMessage;
import com.loremind.domain.generationcontext.ChatRequest;
import com.loremind.domain.generationcontext.LoreStructuralContext;
import com.loremind.domain.generationcontext.LoreStructuralContext.FolderPage;
import com.loremind.domain.generationcontext.PageContext;
import com.loremind.domain.generationcontext.ports.AiChatProvider;
import com.loremind.domain.lorecontext.Lore;
import com.loremind.domain.lorecontext.LoreNode;
import com.loremind.domain.lorecontext.Page;
import com.loremind.domain.lorecontext.Template;
import com.loremind.domain.lorecontext.ports.LoreNodeRepository;
import com.loremind.domain.lorecontext.ports.LoreRepository;
import com.loremind.domain.lorecontext.ports.PageRepository;
import com.loremind.domain.lorecontext.ports.TemplateRepository;
import org.springframework.stereotype.Service;

import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.function.Consumer;
import java.util.stream.Collectors;

/**
 * Use case applicatif : chat conversationnel avec Structural Context d'un Lore.
 *
 * Orchestrateur — charge la carte structurelle (dossiers + pages + templates
 * + tags) depuis le LoreContext, la traduit vers le GenerationContext, puis
 * délègue au port AiChatProvider pour le streaming.
 *
 * Zéro persistance : la conversation est éphémère (responsabilité du frontend).
 */
@Service
public class StreamChatForLoreUseCase {

    private final LoreRepository loreRepository;
    private final LoreNodeRepository loreNodeRepository;
    private final PageRepository pageRepository;
    private final TemplateRepository templateRepository;
    private final AiChatProvider aiChatProvider;

    public StreamChatForLoreUseCase(
            LoreRepository loreRepository,
            LoreNodeRepository loreNodeRepository,
            PageRepository pageRepository,
            TemplateRepository templateRepository,
            AiChatProvider aiChatProvider) {
        this.loreRepository = loreRepository;
        this.loreNodeRepository = loreNodeRepository;
        this.pageRepository = pageRepository;
        this.templateRepository = templateRepository;
        this.aiChatProvider = aiChatProvider;
    }

    /**
     * Streame la réponse du LLM pour le Lore donné avec la conversation fournie.
     *
     * Méthode bloquante : retourne une fois le stream terminé (onComplete ou onError).
     * L'appelant (controller SSE) doit l'exécuter dans un thread dédié.
     *
     * @param loreId obligatoire — l'univers concerné
     * @param pageId optionnel (nullable) — si fourni, focalise l'IA sur cette page
     *               précise (template, champs, valeurs actuelles).
     * @throws IllegalArgumentException si le Lore (ou la Page si pageId fourni) est introuvable
     */
    public void execute(
            String loreId,
            String pageId,
            List<ChatMessage> messages,
            Consumer<String> onToken,
            Runnable onComplete,
            Consumer<Throwable> onError) {

        LoreStructuralContext loreContext = buildLoreContext(loreId);
        PageContext pageContext = (pageId == null || pageId.isBlank())
                ? null
                : buildPageContext(pageId);

        ChatRequest request = ChatRequest.builder()
                .messages(messages)
                .loreContext(loreContext)
                .pageContext(pageContext)
                .build();

        aiChatProvider.streamChat(request, onToken, onComplete, onError);
    }

    // --- Construction du contexte d'une page précise ------------------------

    /**
     * Charge la Page + son Template et construit un PageContext prêt à injecter.
     * Si le template est absent (page orpheline), on renvoie un PageContext
     * minimal (titre + template "?", champs vides) — l'IA reste contextualisée
     * sur la page sans pouvoir proposer de champs précis.
     */
    private PageContext buildPageContext(String pageId) {
        Page page = pageRepository.findById(pageId)
                .orElseThrow(() -> new IllegalArgumentException(
                        "Page non trouvée avec l'ID: " + pageId));

        String templateName = "?";
        List<String> templateFields = Collections.emptyList();
        if (page.hasTemplate()) {
            Template template = templateRepository.findById(page.getTemplateId()).orElse(null);
            if (template != null) {
                templateName = template.getName();
                templateFields = template.getFields() != null
                        ? template.getFields()
                        : Collections.emptyList();
            }
        }

        Map<String, String> values = page.getValues() != null
                ? page.getValues()
                : Collections.emptyMap();

        return PageContext.builder()
                .title(page.getTitle())
                .templateName(templateName)
                .templateFields(templateFields)
                .values(values)
                .build();
    }

    // --- Construction de la carte structurelle ------------------------------

    private LoreStructuralContext buildLoreContext(String loreId) {
        Lore lore = loreRepository.findById(loreId)
                .orElseThrow(() -> new IllegalArgumentException(
                        "Lore non trouvé avec l'ID: " + loreId));

        List<LoreNode> nodes = loreNodeRepository.findByLoreId(loreId);
        List<Page> pages = pageRepository.findByLoreId(loreId);
        List<Template> templates = templateRepository.findByLoreId(loreId);

        Map<String, List<FolderPage>> folders = buildFoldersMap(nodes, pages, templates);
        List<String> tags = extractUniqueTags(pages);

        return LoreStructuralContext.builder()
                .loreName(lore.getName())
                .loreDescription(lore.getDescription())
                .folders(folders)
                .tags(tags)
                .build();
    }

    private Map<String, List<FolderPage>> buildFoldersMap(
            List<LoreNode> nodes, List<Page> pages, List<Template> templates) {

        Map<String, String> templateNameById = templates.stream()
                .collect(Collectors.toMap(Template::getId, Template::getName, (a, b) -> a));

        // LinkedHashMap : préserve l'ordre d'insertion pour un prompt lisible.
        Map<String, List<FolderPage>> folders = new LinkedHashMap<>();
        for (LoreNode node : nodes) {
            folders.put(node.getName(), pagesInFolder(node.getId(), pages, templateNameById));
        }
        return folders;
    }

    private List<FolderPage> pagesInFolder(
            String nodeId, List<Page> allPages, Map<String, String> templateNameById) {
        return allPages.stream()
                .filter(p -> nodeId.equals(p.getNodeId()))
                .map(p -> FolderPage.builder()
                        .title(p.getTitle())
                        .templateName(templateNameById.getOrDefault(p.getTemplateId(), "?"))
                        .build())
                .collect(Collectors.toList());
    }

    private List<String> extractUniqueTags(List<Page> pages) {
        return pages.stream()
                .filter(p -> p.getTags() != null)
                .flatMap(p -> p.getTags().stream())
                .distinct()
                .collect(Collectors.toList());
    }
}
