package org.texttechnologylab.project.gruppe_05_1.rest;

import io.javalin.http.Context;
import io.javalin.openapi.*;
import org.texttechnologylab.project.gruppe_05_1.database.MongoPprUtils;
import org.texttechnologylab.project.gruppe_05_1.domain.html.HtmlSpeech;
import org.texttechnologylab.project.gruppe_05_1.domain.html.ParlamentarierDetails;
import org.texttechnologylab.project.gruppe_05_1.domain.nlp.NamedEntity;
import org.texttechnologylab.project.gruppe_05_1.domain.nlp.Sentiment;
import org.texttechnologylab.project.gruppe_05_1.domain.nlp.Token;
import org.texttechnologylab.project.gruppe_05_1.domain.nlp.Topic;
import org.texttechnologylab.project.gruppe_05_1.domain.speech.SpeechMetaData;
import org.texttechnologylab.project.gruppe_05_1.util.Logger;
import org.texttechnologylab.project.gruppe_05_1.xml.speeches.Interfaces.Speech;

import java.util.*;
import java.util.stream.Collectors;

import static org.texttechnologylab.project.gruppe_05_1.util.PPRUtils.listFractionsFromMembers;

public class SpeechController {
    /**
     * Liste alle Reden eines Parlamentariers an
     * @param ctx Javalin Context
     */
    @OpenApi(
            summary = "Liste alle Reden eines Parlamentariers an",
            description = "Liste alle Reden eines Parlamentariers an",
            operationId = "listSpeeches",
            path = "/reden/{id}",
            methods = HttpMethod.GET,
            tags = {"Rede"},
            pathParams = {
                    @OpenApiParam(name = "id", description = "id des Parlamentariers", required = true),
            },
            responses = {
                    @OpenApiResponse(status = "200", content = {@OpenApiContent(from = Speech[].class)})
            })
    public static void listSpeeches(Context ctx) {
        String parlamentarierId = ctx.pathParam("id");

        ParlamentarierDetails p = MongoPprUtils.getParlamentarierDetailsByID(parlamentarierId);
        List<SpeechMetaData> speechMetaDataList = MongoPprUtils.getSpeechesMetadataForSpeaker(parlamentarierId);

        Map<String, Object> attributes = new HashMap<>();
        attributes.put("p", p);
        attributes.put("speechesMetaDataList", speechMetaDataList);
        ctx.render("showSpeechesList.ftl", attributes);
    }

    /**
     * Zeige eine bestimmte Rede des Parlamentariers an
     * @param ctx Javalin Context
     */
    @OpenApi(
            summary = "Zeige eine bestimmte Rede des Parlamentariers an",
            description = "Zeige eine bestimmte Rede des Parlamentariers an",
            operationId = "showSpeech",
            path = "/reden/{id}/{redeID}",
            methods = HttpMethod.GET,
            tags = {"Rede"},
            pathParams = {
                    @OpenApiParam(name = "id", description = "id des Parlamentariers", required = true),
                    @OpenApiParam(name = "redeId", description = "id der Rede", required = true),
            },
            responses = {
                    @OpenApiResponse(status = "200", content = {@OpenApiContent(from = Speech.class)})
            })
    public static void showSpeech(Context ctx) {
        String parlamentarierId = ctx.pathParam("id");
        String redeId = ctx.pathParam("redeId");

        Map<String, Object> attributes = new HashMap<>();

        HtmlSpeech speech = MongoPprUtils.getSpeechByKey(redeId);
        if (speech == null) {
            attributes.put("error", "Rede " + redeId + " nicht vorhanden");
            ctx.render("speech.ftl", attributes);
            return;
        }
        attributes.put("s", speech);

        // Foto des Abgeordnetes
        String picture = MongoPprUtils.getParlamentarierPictureByID(parlamentarierId);
        attributes.put("picture", picture);

        // NLP
        if (speech.getNlp() != null) {

            // NLP: Topic
            if ((speech.getNlp().getTopics() != null) && (speech.getNlp().getTopics().size() > 0)) {
                Map<String, Double> topics = Topic.condenseTopicInformation(speech.getNlp().getTopics());   // Daten "verdichten"...
                // ... und ersetzen
                speech.getNlp().setTopics(
                        topics.entrySet().stream()
                                .map(me -> new Topic(me.getKey(), me.getValue(), null))
                                .collect(Collectors.toList()));
            } else {
                speech.getNlp().setTopics(null);
            }

            // NLP: POS
            if (speech.getNlp() != null && speech.getNlp().getTokens() != null) {
                List<Token> tokens = speech.getNlp().getTokens();

                Map<String, Integer> posCounts = Token.countPOS(tokens);

                List<Token> posList = posCounts.entrySet().stream()
                        .map(entry -> new Token(entry.getKey(), String.valueOf(entry.getValue()), "")) // Lemma remains empty
                        .collect(Collectors.toList());

                Logger.debug("Sending POS List to NLP - " + posList);

                speech.getNlp().setPosList((List) posList);

            } else {
                Logger.debug("POS List is EMPTY");
                speech.getNlp().setPosList((List) new ArrayList<Token>()); // Ensure it's never null
            }

            // NLP: Named Entities
            if ((speech.getNlp().getNamedEntities() != null)
                    && (speech.getNlp().getNamedEntities().size() > 0)) {

                Map<String, Map<String, Integer>> namedEntitiesMapOfMaps = new HashMap<>();

                for (NamedEntity ne : speech.getNlp().getNamedEntities()) {
                    String type = ne.getType();
                    String text = ne.getText();

                    if (namedEntitiesMapOfMaps.containsKey(type)) {
                        // Named Entity Type bekannt...
                        Map<String, Integer> typeAppearance = namedEntitiesMapOfMaps.get(type);
                        if (typeAppearance.containsKey(text)) {
                            // ... und der Text auch bekannt --> erhöhe die Anzahl um 1
                            typeAppearance.replace(
                                    text,
                                    typeAppearance.get(text) + 1) ;
                        } else {
                            typeAppearance.put(text, 1);
                        }
                    } else {
                        // Named Entity Type unbekannt: erstelle einen neuen Eintrag für Type sowie einen Eintrag für den ihm gehörigen Text
                        Map<String, Integer> firstTextAppearance = new HashMap<>();
                        firstTextAppearance.put(text, 1);
                        namedEntitiesMapOfMaps.put(type, firstTextAppearance);
                    }
                }

                attributes.put("na_info", namedEntitiesMapOfMaps);
            } else {
                attributes.put("na_info", null);
            }

            // TODO: Token wird momentan etwas komisch abgespeichert, da im Attribut text die POS art steht, und in pos die Anzahl dieser POS arten. Umstrukturieren damit keine Verwirrung herrscht

            // NLP: Sentiments
            // Der erste Sentiment gilt der gesamten Rede. Die weitere Sentiments entsprechen die Sätze.
            List<Sentiment> sentiments = speech.getNlp().getSentiments();
            if ((sentiments != null) && ! sentiments.isEmpty()) {
                Sentiment overallSentiment = sentiments.get(0);
                attributes.put("overallSentiment", overallSentiment);
                sentiments.remove(0);

                // Sentiment-Icon
                List<String> sentimentIcons = calculateSentimentIcons(sentiments);
                attributes.put("sentimentIcons", sentimentIcons);
            } else {
                attributes.put("overallSentiment", null);
                attributes.put("sentimentIcons", null);
            }
            attributes.put("sentiments", sentiments);

        }

        ctx.render("speech.ftl", attributes);
    }


    private static String POSITIVE_SENTIMENT= "fas fa-thumbs-up sentiment-positive";      // Alternativ:  fas fa-smile positive
    private static String NEGATIVE_SENTIMENT= "fas fa-thumbs-down sentiment-negative";     // Alternativ: fas fa-frown negative
    private static String NEUTRAL_SENTIMENT = "fas fa-meh sentiment-neutral";             // Alternativ: fas fa-circle neutral

    /**
     * Ordne einem Satz ein Sentiment-Icon
     * @param sentiments
     * @return Icon Name (als Font Awesome Icon)
     */
    private static List<String> calculateSentimentIcons(List<Sentiment> sentiments) {
        double threshold = 0.15;
        List<String> iconNames = new ArrayList<>();
        for (Sentiment s: sentiments) {
            if (s.getPositive() - s.getNegative() > threshold) {
                iconNames.add(POSITIVE_SENTIMENT);
            } else if (s.getNegative() - s.getPositive() > threshold) {
                iconNames.add(NEGATIVE_SENTIMENT);
            } else iconNames.add(NEUTRAL_SENTIMENT);
        }
        return iconNames;
    }

    @OpenApi(
            summary = "Liste alle Reden (Filtern ist möglich)",
            description = "Liste alle Reden. Man kann nach Freitext (MdB Name, Partei/Fraktion) oder nach Thema (Topic) filtern",
            operationId = "listAllSpeeches",
            path = "/reden",
            methods = HttpMethod.GET,
            tags = {"Rede"},
            queryParams = {
                    @OpenApiParam(name = "filter", description = "Full-Text-Filter. Kann Vorname, Nachname oder Partei filtern", required = false),
                    // TODO: Topic Filter
            },
            responses = {
                    @OpenApiResponse(status = "200", content = {@OpenApiContent(from = Speech[].class)})
            })
    public static void listAllSpeeches(Context ctx) {
        String filter = ctx.queryParam("filter");
        Logger.info("Filter: '" + filter + "'");

        List<SpeechMetaData> speechMetaDataList = MongoPprUtils.getSpeechesMetadata(filter);

        Map<String, Object> attributes = new HashMap<>();
        attributes.put("speechesMetaDataList", speechMetaDataList);

        // Filtern nach Text
        attributes.put("filter", filter == null || filter.isBlank() ? null : filter);

        // Filtern nach Partei/Fraktion
        attributes.put("parties", listFractionsFromMembers(MongoPprUtils.getAllParlamentarier("")));

        // Filtern nach Topics - TODO
        List<String> topics = Arrays.asList("International", "Government", "Labor", "Economy", "Public");
        attributes.put("topics", topics);

        ctx.render("showAllSpeechesList.ftl", attributes);
    }
}
