import sys; sys.path.append("..")
from selenium.webdriver.firefox.options import Options
from selenium.webdriver.firefox.service import Service
from selenium import webdriver
import time
import random
import os
import uuid
import glob

# In dieser Klasse wird ein Selenium-WebDriver für Firefox verwaltet, um Seiten zu laden und zu steuern.
class SeleniumManager:

    # In diesem Konstruktor werden die Firefox-Optionen eingerichtet und der WebDriver initialisiert.
    def __init__(
        self,
        windowSize: str = "1920,1080",
        headless: bool = True,
        disableGpu: bool = True,
        noSandbox: bool = True,
        disableDevShmUsage: bool = True,
        geckodriverPath: str = '/usr/bin/geckodriver'
    ):

        # In dieser Variablen werden die Firefox-Optionen gespeichert.
        firefoxOptions = Options()

        # Hier wird die Fenstergröße auf den angegebenen Wert festgelegt.
        firefoxOptions.add_argument(f"--window-size={windowSize}")

        # In dieser Abzweigung wird geprüft, ob der Browser im Headless-Modus laufen soll.
        if headless:
            firefoxOptions.add_argument("--headless")

        # In dieser Anweisung wird die GPU deaktiviert, wenn das Flag gesetzt ist.
        firefoxOptions.add_argument("--disable-gpu" if disableGpu else "")

        # In dieser Anweisung wird die Sandbox deaktiviert, wenn das Flag gesetzt ist.
        firefoxOptions.add_argument("--no-sandbox" if noSandbox else "")

        # In dieser Anweisung wird dev-shm usage deaktiviert, wenn das Flag gesetzt ist.
        firefoxOptions.add_argument("--disable-dev-shm-usage" if disableDevShmUsage else "")

        # Hier wird ein zufälliger User-Agent erzeugt und in die Firefox-Optionen gesetzt.
        firefoxOptions.set_preference("general.useragent.override", self.getRandomUserAgent())

        # In dieser Variablen wird ein Service-Objekt für den Geckodriver mit dem angegebenen Pfad erzeugt.
        firefoxService = Service(geckodriverPath)

        # Hier wird der Firefox-WebDriver mit dem Dienst und den Optionen initialisiert.
        self.driver = webdriver.Firefox(service=firefoxService, options=firefoxOptions)

    # In dieser Methode wird eine Seite geladen und der HTML-Inhalt im Cache abgelegt.
    def simpleRequest(self, url):

        # Hier wird die gewünschte URL aufgerufen.
        self.driver.get(url)

        # In dieser Variablen wird der gesamte HTML-Quellcode der aktuell geladenen Seite gespeichert.
        htmlContent = self.driver.page_source

        # Hier wird der relative Pfad zum Cache-Verzeichnis ermittelt.
        cacheDir = os.path.join(os.path.dirname(__file__), "..", "cache")

        # In dieser Abzweigung wird das Cache-Verzeichnis erstellt, falls es nicht vorhanden ist.
        if not os.path.exists(cacheDir):
            os.makedirs(cacheDir)

        # Hier wird ein eindeutiger Dateiname auf Basis einer UUID erzeugt.
        uniqueFilename = f"{uuid.uuid4().hex}.html"

        # In dieser Variablen wird der vollständige Pfad für die Cache-Datei erzeugt.
        filePath = os.path.join(cacheDir, uniqueFilename)

        # Hier wird die HTML-Datei im Cache-Verzeichnis geschrieben.
        with open(filePath, "w", encoding="utf-8") as f:
            f.write(htmlContent)

        # In dieser Variablen werden alle HTML-Dateien im Cache-Verzeichnis gesammelt.
        htmlFiles = glob.glob(os.path.join(cacheDir, "*.html"))

        # Hier wird die Liste nach Änderungszeit sortiert, absteigend (neueste zuerst).
        htmlFiles.sort(key=os.path.getmtime, reverse=True)

        # In dieser Schleife werden alle Dateien außer den 10 aktuellsten entfernt.
        for oldFile in htmlFiles[10:]:
            try:
                os.remove(oldFile)
            except Exception as e:
                print(f"Fehler beim Löschen der Datei {oldFile}: {e}")

        # Hier wird der WebDriver (Selenium-Treiber) zurückgegeben, um weiter damit arbeiten zu können.
        return self.driver

    # In dieser Methode wird die Seite durch mehrmaliges Scrollen bis zum Seitenende geladen, um dynamische Inhalte sichtbar zu machen.
    def performScroll(self, scrollStep=600, scrollPause=0.01, maxTries=10):

        # Hier wird die Anfangshöhe des Dokumentbodens ermittelt.
        lastHeight = self.driver.execute_script("return document.body.scrollHeight")

        # In dieser Variablen wird gezählt, wie oft sich die Höhe nicht ändert.
        stopCounter = 0

        # In dieser Schleife wird eine bestimmte Anzahl von Scroll-Versuchen durchgeführt.
        for _ in range(maxTries):

            # In dieser Schleife wird schrittweise bis zur aktuellen Höhe gescrollt.
            for position in range(0, lastHeight, scrollStep):
                self.driver.execute_script(f"window.scrollTo(0, {position});")
                time.sleep(scrollPause)

            # Hier wird am Ende des Durchlaufs einmal ganz an das Seitenende gescrollt.
            self.driver.execute_script("window.scrollTo(0, document.body.scrollHeight);")
            time.sleep(scrollPause)

            # In dieser Variablen wird die neue Seitenhöhe ermittelt.
            newHeight = self.driver.execute_script("return document.body.scrollHeight")

            # In dieser Abzweigung wird geprüft, ob sich die Höhe im Vergleich zum letzten Durchlauf nicht verändert hat.
            if newHeight == lastHeight:
                stopCounter += 1

                # In dieser Abzweigung wird die Schleife beendet, wenn sich die Höhe wiederholt nicht ändert.
                if stopCounter >= 2:
                    break
            else:
                stopCounter = 0

            # Hier wird die zuletzt bekannte Höhe auf den neuen Wert aktualisiert.
            lastHeight = newHeight

        # Hier wird der WebDriver zurückgegeben, um die Interaktion fortzusetzen.
        return self.driver

    # In dieser Methode wird der Selenium-WebDriver geschlossen und der Browser beendet.
    def closeDriver(self):
        self.driver.quit()

    # In dieser Methode wird ein zufälliger User-Agent erzeugt, um beim Surfen unterschiedliche Browser zu simulieren.
    def getRandomUserAgent(self):

        # In dieser Liste werden verschiedene Betriebssystem-Angaben für den User-Agent hinterlegt.
        operatingSystems = [
            "Windows NT 10.0; Win64; x64",
            "Windows NT 6.1; Win64; x64",
            "Macintosh; Intel Mac OS X 10_15_7",
            "Macintosh; Intel Mac OS X 13_3_1",
            "X11; Linux x86_64",
            "iPhone; CPU iPhone OS 16_0 like Mac OS X",
            "Android 11; Mobile; rv:68.0"
        ]

        # In dieser Liste werden verschiedene Browsertypen aufgeführt.
        browserTypes = ["chrome", "firefox", "safari", "edge", "opera"]

        # Hier wird ein Zufallswert für das Betriebssystem ausgewählt.
        chosenOs = random.choice(operatingSystems)

        # Hier wird ein Zufallswert für den Browser ausgewählt.
        chosenBrowser = random.choice(browserTypes)

        # Diese Variablen repräsentieren zufällige Versionsnummern, um den User-Agent realistischer zu gestalten.
        majorVersion = random.randint(60, 120)
        minorVersion = random.randint(0, 4000)
        buildVersion = random.randint(0, 999)
        patchVersion = random.randint(0, 99)

        # In dieser Abzweigung wird ein Chrome-User-Agent generiert.
        if chosenBrowser == "chrome":
            userAgent = (
                f"Mozilla/5.0 ({chosenOs}) AppleWebKit/537.36 "
                f"(KHTML, like Gecko) Chrome/{majorVersion}.{buildVersion}.{minorVersion}.{patchVersion} "
                f"Safari/537.36"
            )

        # In dieser Abzweigung wird ein Firefox-User-Agent generiert.
        elif chosenBrowser == "firefox":
            rvVersion = f"{majorVersion}.0"
            userAgent = (
                f"Mozilla/5.0 ({chosenOs}; rv:{rvVersion}) "
                f"Gecko/20100101 Firefox/{rvVersion}"
            )

        # In dieser Abzweigung wird ein Safari-User-Agent generiert.
        elif chosenBrowser == "safari":
            safariVersion = f"{majorVersion}.{random.randint(0, 9)}"
            webkitVersion = f"605.1.{random.randint(0, 99)}"
            userAgent = (
                f"Mozilla/5.0 ({chosenOs}) AppleWebKit/{webkitVersion} "
                f"(KHTML, like Gecko) Version/{safariVersion} Safari/{webkitVersion}"
            )

        # In dieser Abzweigung wird ein Edge-User-Agent generiert.
        elif chosenBrowser == "edge":
            blinkVersion = f"{majorVersion}.{buildVersion}.{minorVersion}.{patchVersion}"
            userAgent = (
                f"Mozilla/5.0 ({chosenOs}) AppleWebKit/537.36 "
                f"(KHTML, like Gecko) Chrome/{blinkVersion} Safari/537.36 Edg/{blinkVersion}"
            )

        # In dieser Abzweigung wird ein Opera-User-Agent generiert.
        else:
            blinkVersion = f"{majorVersion}.{buildVersion}.{minorVersion}.{patchVersion}"
            userAgent = (
                f"Mozilla/5.0 ({chosenOs}) AppleWebKit/537.36 "
                f"(KHTML, like Gecko) Chrome/{blinkVersion} Safari/537.36 OPR/{blinkVersion}"
            )

        # Hier wird der generierte User-Agent zurückgegeben.
        return userAgent
