package org.texttechnologylab.project.gruppe_05_1.util;

import com.mongodb.client.MongoCollection;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.jsoup.select.Elements;
import org.texttechnologylab.project.gruppe_05_1.database.MongoDBHandler;
import org.texttechnologylab.project.gruppe_05_1.database.MongoObjectFactory;
import org.texttechnologylab.project.gruppe_05_1.database.MongoPprUtils;
import org.texttechnologylab.project.gruppe_05_1.domain.html.Parlamentarier;
import org.texttechnologylab.project.gruppe_05_1.domain.mdb.BiografischeAngaben;
import org.texttechnologylab.project.gruppe_05_1.domain.mdb.Mdb;
import org.texttechnologylab.project.gruppe_05_1.domain.speaker.Membership;
import org.texttechnologylab.project.gruppe_05_1.domain.speaker.Speaker;
import org.texttechnologylab.project.gruppe_05_1.xml.FileObjectFactory;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.xml.sax.InputSource;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import java.io.*;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;

import static org.texttechnologylab.project.gruppe_05_1.Main.FORCE_UPLOAD_MEMBERS;

public abstract class PPRUtils {

    public static final String PARTEILOS_KUERZEL = "Parteilos";
    private static final Set<String> processedProtocols = new HashSet<>();
    private static final Set<org.w3c.dom.Document> xmlProtocols = new HashSet<>();
    public static final Integer legislaturPeriode = 20;



    /**
     * Alle Informationen lesen...
     *      - Parlamentarier
     *      - Reden
     *      - Kommentare
     *      - etc.
     * ... und in die Mongo-DB persistieren, falls noch nicht vorhanden sind.
     * @param xmlFactory Factory für die XML-Objekte
     * @param mongoFactory Factory für die MongoDB-Objekte
     */
    public static void parlamentExplorerInit(FileObjectFactory xmlFactory, MongoObjectFactory mongoFactory) {
        Properties xmlProperties = PropertiesUtils.readPropertiesFromResource("config/xml.properties");

        // MdB lesen: MDBs werden nur dann gelesen und persistiert, wenn die Collection noch leer ist
        String mdbUrl = xmlProperties.getProperty("mdb_url");
        if (MongoPprUtils.getSpeakerCollection().countDocuments() == 0 || FORCE_UPLOAD_MEMBERS) {
            readAndPersistMdbs(mdbUrl, xmlFactory, mongoFactory);
        } else {
            Logger.warn("Members already in the DB. Skipping...");
        }

        // Reden und Kommentare einlesen und persistieren - TODO
        readSpeechesAndComments(xmlFactory, mongoFactory);

        // Fotos hochladen - TODO
        readPhotos(mongoFactory);

        // NLP-Analyse (Text und Video)
        // TODO: Anpassung notwendig, Daten aus dem NEtz statt aus resources holen
        // Achtung: läuft nicht unter Windows. Verwende Linux (nativ oder im Virtual Box)
        // NlpUtils.createNlpData();

    }

    /**
     * Fotos hochladen - TODO
     * @param mongoFactory Factory für die MongoDB-Objekte
     */
    public static void readPhotos(MongoObjectFactory mongoFactory) {

    }

    /**
     * Reden und Kommentare einlesen - TODO
     * @param xmlFactory Factory für die XML-Objekte
     * @param mongoFactory Factory für die MongoDB-Objekte
     */
    public static void readSpeechesAndComments(FileObjectFactory xmlFactory, MongoObjectFactory mongoFactory) {

    }

    /**
     * Liest die MdBs aus der Bundestag-Seite und persistiere sie in die MongoDB
     * @param mdbUrl URL der MDBs
     * @param xmlFactory Factory für die XML-Objekte
     */
    public static void readAndPersistMdbs(String mdbUrl, FileObjectFactory xmlFactory, MongoObjectFactory mongoFactory) {
        org.w3c.dom.Document mdbRoot = getMdbFromRemoteXmlZipfile(mdbUrl);
        Element rootElement = mdbRoot.getDocumentElement();
        // TODO: optional! persist the metadata of the    <VERSION>1723434311</VERSION>    element

        List<Node> mdbNodes = XmlUtils.getChildrenByName(rootElement, "MDB");

        MongoCollection<org.bson.Document> speakerCollection = MongoPprUtils.getSpeakerCollection();
        for (Node mdbNode : mdbNodes) {
            Speaker speaker = xmlFactory.createSpeaker(mdbNode);
            // System.out.println("Speaker " + speaker.getId() + " (" + speaker.getFirstName() + " " + speaker.getName() + ", " + speaker.getParty() + ")");

            if (mdbActiveInWp(speaker, legislaturPeriode)) {
                org.bson.Document speakerDoc = mongoFactory.createSpeaker(speaker);
                MongoDBHandler.insertDocument(speakerCollection, speakerDoc);
            }
        }

        // TODO: persist each speaker!
        // TODO: guard: if not in the DB already

    }

    // TODO - HERE
    private static boolean mdbActiveInWp(Speaker speaker, Integer legislaturPeriode) {
        List<Integer> wps = speaker.getMemberships().stream()
                .map(Membership::getWp)
                .toList();

        return wps.contains(legislaturPeriode);
    }

    /**
     * Liest die MDB aus einer Zip-Datei der bundestag,de-Seite
     * @param zipUrl URL der ZIP-Datei
     * @return the MDB Document
     */
    private static org.w3c.dom.Document getMdbFromRemoteXmlZipfile(String zipUrl) {
        URL url;
        InputStream urlInputStream;
        try {
            url = new URL(zipUrl);
            urlInputStream = url.openStream();
            ZipInputStream zipInputStream = new ZipInputStream(urlInputStream);
            ZipEntry entry;

            // Temporary storage for XML and DTD content
            ByteArrayOutputStream xmlOutputStream = new ByteArrayOutputStream();
            ByteArrayOutputStream dtdOutputStream = new ByteArrayOutputStream();

            while ((entry = zipInputStream.getNextEntry()) != null) {
                String entryName = entry.getName();
                if (entryName.equals("MDB_STAMMDATEN.XML")) {
                    copyStream(zipInputStream, xmlOutputStream); // copy the XML InputStream
                } else if (entryName.equals("MDB_STAMMDATEN.DTD")) {
                    copyStream(zipInputStream, dtdOutputStream);// copy theDTD InputStream
                }
                zipInputStream.closeEntry();
            }

            if (xmlOutputStream.size() == 0 || dtdOutputStream.size() == 0) { // TODO
                // throw new FileNotFoundException("XML or DTD not found in the ZIP archive");
            }
            InputStream xmlInputStreamFinal = new ByteArrayInputStream(xmlOutputStream.toByteArray());
            InputStream dtdInputStreamFinal = new ByteArrayInputStream(dtdOutputStream.toByteArray());



            org.w3c.dom.Document doc = XmlUtils.loadXmlDocumentFromRemoteZip(entry, xmlInputStreamFinal, dtdInputStreamFinal);
            // Cleanup
            zipInputStream.close();
            urlInputStream.close();
            return doc;

        } catch (IOException e) {
            // TODO
            throw new RuntimeException(e);
        }
    }

    /**
     * Helper method to save a stream for a later use.
     * We use it to save the DTD and the XML files of the MDBs, which are within a zipfile on the bundestag.de site
     * @param input the input stream
     * @param output the output stream
     * @throws IOException if an error occurs
     */

    private static void copyStream(InputStream input, OutputStream output) throws IOException {
        byte[] buffer = new byte[1024];
        int bytesRead;
        while ((bytesRead = input.read(buffer)) != -1) {
            output.write(buffer, 0, bytesRead);
        }
    }


    /**
     * Eine Liste von Parlamentariern nach Namen (erst nach Nachnamen, dann nach Vornamen) sortieren (aufsteigend)
     * @param mdbList Liste der Parlamentarier
     */
    public static void sortParlamentarierByName(List<Parlamentarier> mdbList) {

        mdbList.sort((o1, o2) -> {
            int compareNachname = o1.getNachname().compareTo(o2.getNachname());
            if (0 == compareNachname) {
                return o1.getVorname().compareTo(o2.getVorname());
            } else {
                return compareNachname;
            }
        });
    }

    // TODO: altes Zeug, sortieren...

    /**
     * Alle Parteien (aus einer Liste der MdBs) herausfinden.
     * null-Einträge durch einen Platzhalter ersetzen, damit später keine null pointer exceptions auftretten
     * @param mdbList Liste der MdBs
     * @return Set der Parteien
     */
    public static Set<String> getFraktionenFromMdbList(List<Mdb> mdbList) {

        /* mdb --> BIOGRAFISCHE_ANGABEN --> PARTEI_KURZ ergibt die aussagekräftige Ergebnisse
         * parteiKurz: [null, DRP, SPD, DPS, DP, WAV, BP, FU, SRP, CSUS, FDP, DIE LINKE., CDU, PDS/LL, GRÜNE,
         *             PDS, AfD, CSU, DIE GRÜNEN/BÜNDNIS 90, Plos, DSU, BSW, BÜNDNIS 90/DIE GRÜNEN, LKR,
         *             GB/ BHE, SSW, DPB, Die PARTEI, DZP, CVP, KPD]
         */

        Set<String> parteikuerzel=
        mdbList.stream()
                .map(Mdb::getBio)
                .map(BiografischeAngaben::getParteiKuerzel)
                .collect(Collectors.toSet());

        // Parteilose dabei?
        if (parteikuerzel.contains(null)) {
            parteikuerzel.remove(null);
            parteikuerzel.add(PARTEILOS_KUERZEL);
        }

        return parteikuerzel;
    }

    /**
     * Eine Zuordnung zwischen Partei und deren Mitgliedern erzeugen. Ein Mitglied ist hier die MDB-Struktur
     * @param mdbList Liste der MdBs
     * @return Map mit Partei als Key und Liste der MdBs als Value
     */
    public static Map<String, List<Mdb>> createMdbParteiZuordnung(List<Mdb> mdbList) {

        Map<String, List<Mdb>> zuordnung = new HashMap<>();

        for (Mdb mdb : mdbList) {
            String partei = mdb.getBio().getParteiKuerzel();
            if (null == partei) {
                partei = PARTEILOS_KUERZEL;
            }
            if (zuordnung.containsKey(partei)) {
                zuordnung.get(partei).add(mdb);
            } else {
                zuordnung.put(partei, new ArrayList<>());
                zuordnung.get(partei).add(mdb);
            }

        }

        return zuordnung;
    }

    /**
     * Eine Zuordnung zwischen Partei und deren Mitgliedern erzeugen. Ein Mitglied wird hier durch seine ID erfaßt
     * @param parteien Set der Parteien
     * @param mdbList Liste der MdBs
     * @return Map mit Partei als Key und Liste der MdB-IDs als Value
     */
    public static Map<String, List<String>> getMdbParteiZuordnung(Set<String> parteien, List<Mdb> mdbList) {

        Map<String, List<String>> zuordnung = new HashMap<>();

        for (String partei : parteien) {
            if (partei.equals(PARTEILOS_KUERZEL)) {
                List<String> ids = mdbList.stream()
                        .filter(mdb -> (null == mdb.getBio().getParteiKuerzel()))
                        .map(Mdb::getId)
                        .collect(Collectors.toList());
                zuordnung.put(partei, ids);
            } else {
                List<String> ids = mdbList.stream()
                        .filter(mdb -> (( null != mdb.getBio().getParteiKuerzel() ) && (mdb.getBio().getParteiKuerzel().equals(partei))))
                        .map(Mdb::getId)
                        .collect(Collectors.toList());
                zuordnung.put(partei, ids);
            }
        }

        return zuordnung;
    }

    /**
     * Eine Zuordnung zwischen MdB (repräsentiert durch seine ID) und seien biographischen Daten erzeugen.
     * @param mdbList Liste der MdBs
     * @return Map mit MdB-ID als Key und biographischen Daten als Value
     */
    public static Map<String, BiografischeAngaben> getMdbParteiZuordnung(List<Mdb> mdbList) {

        Map<String, BiografischeAngaben> zuordnung = new HashMap<>();

        for (Mdb mdb : mdbList) {
            zuordnung.put(mdb.getId(), mdb.getBio());
        }

        return zuordnung;
    }

    /**
     * Eine Liste von MdBs nach Namen (erst nach Nachnamen, dann nach Vornamen) sortieren (aufsteigend)
     * @param mdbList Liste der MdBs
     */
    public static void sortMdbByName(List<Mdb> mdbList) {

        mdbList.sort((o1, o2) -> {
            int compareNachname = o1.getNamen().get(0).getNachname().compareTo(o2.getNamen().get(0).getNachname());
            if (0 == compareNachname) {
                return o1.getNamen().get(0).getVorname().compareTo(o2.getNamen().get(0).getVorname());
            } else {
                return compareNachname;
            }
        });
    }


    public static Set<org.w3c.dom.Document> processXML() {
        int offset = 0;
        int limit = 10;
        boolean hasMore = true;
        while (hasMore) {
            String queryUrl = "https://www.bundestag.de/ajax/filterlist/de/services/opendata/866354-866354?limit="
                    + limit + "&noFilterSet=true&offset=" + offset;
            //System.out.println("Lade: " + queryUrl);
            try {
                Document htmlDoc = Jsoup.connect(queryUrl).get();
                Elements xmlLinks = htmlDoc.select("a.bt-link-dokument");
                if (xmlLinks.isEmpty()) {
                    Logger.info("No further protocols found");
                    break;
                }

                for (org.jsoup.nodes.Element link : xmlLinks) {
                    String xmlUrl = link.attr("href");
                    //System.out.println("Verarbeite XML: " + xmlUrl);
                    try {
                        org.w3c.dom.Document xmlDoc = downloadAndParseXML(xmlUrl);
                        String uniqueId = xmlDoc.getDocumentElement().getAttribute("sitzung-nr");
                        if (processedProtocols.contains(uniqueId)) {
                            Logger.warn("Protocol already processed: " + uniqueId);
                            continue;
                        }
                        processedProtocols.add(uniqueId);
                        xmlProtocols.add(xmlDoc);
                    } catch (Exception e) {
                        Logger.error("Fehler beim Verarbeiten der XML-Datei: " + xmlUrl);
                        Logger.error("Fehler: " + e.getLocalizedMessage());
                    }
                }

                // check if next
                org.jsoup.nodes.Element metaSlider = htmlDoc.selectFirst("div.meta-slider");
                if (metaSlider != null && metaSlider.hasAttr("data-nextoffset")) {
                    int nextOffset = Integer.parseInt(metaSlider.attr("data-nextoffset"));
                    if (nextOffset <= offset) {
                        hasMore = false;
                    } else {
                        offset = nextOffset;
                    }
                } else {
                    hasMore = false;
                }
            } catch (IOException e) {
                Logger.error("Fehler beim Laden der Seite: " + queryUrl);
                Logger.error("Fehler: " + e.getLocalizedMessage());
                break;
            }
        }
        return xmlProtocols;
    }

    /**
     * Lädt die XML-Datei von der gegebenen URL herunter und parst sie
     * mittels dbParser.
     *
     * @param xmlUrl URL der XML-Datei
     * @return Das geparste org.w3c.dom.Document
     * @throws Exception wenn ein Fehler auftritt
     */
    public static org.w3c.dom.Document downloadAndParseXML(String xmlUrl) throws Exception {
        URL url = new URL(xmlUrl);
        HttpURLConnection connection = (HttpURLConnection) url.openConnection();
        connection.setRequestMethod("GET");
        connection.connect();

        DocumentBuilderFactory dbFactory = DocumentBuilderFactory.newInstance();
        DocumentBuilder dBuilder = dbFactory.newDocumentBuilder();
        dBuilder.setEntityResolver((publicId, systemId) -> {
            InputStream dtdStream = PPRUtils.class.getResourceAsStream("/plenarprotokolle/dbtplenarprotokoll.dtd");
            if (dtdStream != null) {
                return new InputSource(dtdStream);
            }
            return null;
        });
        org.w3c.dom.Document doc = dBuilder.parse(connection.getInputStream());
        doc.getDocumentElement().normalize();
        return doc;
    }

    public static ArrayList<String> listFilesInDirectory(String directory) {
        File folder = new File(directory);
        File[] files = folder.listFiles();
        ArrayList<String> fileNames = new ArrayList<>();
        if (files != null) {
            for (File file : files) {
                if (file.isFile()) {
                    fileNames.add(file.getName());
                }
            }
        }
        return fileNames;
    }

    public static Set<org.w3c.dom.Document> checkAndProcessNewProtocols(MongoDBHandler mongoDBHandler) {
        Set<org.w3c.dom.Document> newProtocols = new HashSet<>();
        int offset = 0;
        int limit = 10;
        boolean hasMore = true;
        Pattern sessionPattern = Pattern.compile("Plenarprotokoll der (\\d+)\\. Sitzung");

        while (hasMore) {
            String queryUrl = "https://www.bundestag.de/ajax/filterlist/de/services/opendata/866354-866354?limit="
                    + limit + "&noFilterSet=true&offset=" + offset;
            try {
                org.jsoup.nodes.Document htmlDoc = Jsoup.connect(queryUrl).get();
                Elements sessionLinks = htmlDoc.select("a.bt-link-dokument");
                if (sessionLinks.isEmpty()) break;

                for (org.jsoup.nodes.Element link : sessionLinks) {
                    String xmlUrl = link.attr("href");
                    String fileName = xmlUrl.substring(xmlUrl.lastIndexOf('/') + 1);  // "20212.xml"
                    // Entferne die Dateiendung
                    String sessionNumberFull = fileName.replace(".xml", ""); // z.B. "20212"
                    String sessionNumber;
                    if (sessionNumberFull.equals("20007") || sessionNumberFull.equals("20212")) {
                        Logger.warn("Skipping faulty protocol: " + sessionNumberFull);
                        continue;
                    }
                    if (sessionNumberFull.startsWith("20") && sessionNumberFull.length() > 2) {
                        sessionNumber = sessionNumberFull.substring(2);
                    } else {
                        sessionNumber = sessionNumberFull;
                    }
                        if (!mongoDBHandler.sessionExists(sessionNumber)) {
                            try {
                                org.w3c.dom.Document xmlDoc = downloadAndParseXML(xmlUrl);
                                newProtocols.add(xmlDoc);
                            } catch (Exception ex) {
                                Logger.error("Error processing XML for session " + sessionNumber + ": " + ex.getMessage());
                            }
                        }

                }

                org.jsoup.nodes.Element metaSlider = htmlDoc.selectFirst("div.meta-slider");
                if (metaSlider != null && metaSlider.hasAttr("data-nextoffset")) {
                    int nextOffset = Integer.parseInt(metaSlider.attr("data-nextoffset"));
                    if (nextOffset <= offset) {
                        hasMore = false;
                    } else {
                        offset = nextOffset;
                    }
                } else {
                    hasMore = false;
                }
            } catch (IOException e) {
                Logger.error("Error loading page: " + queryUrl + " : " + e.getMessage());
                break;
            }
        }
        return newProtocols;
    }

    public static ArrayList<String> listFractionsFromMembers(List<Parlamentarier> mdbList) {
        ArrayList<String> fractions = new ArrayList<>();
        for (Parlamentarier parlamentarier : mdbList) {
            if (parlamentarier.getPartei() != null) {
                if (!fractions.contains(parlamentarier.getPartei())) {
                    fractions.add(parlamentarier.getPartei());
                }
            } else {
                if (!fractions.contains(parlamentarier.getPartei())) {
                    fractions.add(PARTEILOS_KUERZEL);
                }
            }
        }
        return fractions;
    }
}
