<?php

/*
ET-Chat Copyright by SEDesign | ET-Chat v3.x.x Lizenz: CCPL | Autor: Evgeni Tcherkasski (info@s-e-d.de)
ET-Chat_T-FISH-MOD Weiterentwicklungen, Optimierungen und Ergänzungen am Code sind das geistige Eigentum von Tommy (Thomas Kuhn) und Harlekin (Bernd Witte).

Die darauf folgende Optimierungen und Ergänzungen am Code sind das Geistige Eigentum von Powermen (Christian K.) 
PHP 8/9 Ready, XML Parser, PDO Prepared Statements Get Set sicherheit usw.
*/

class AdminUpDate extends DbConectionMaker
{

	private string $moduleName = 'AdminUpDate';

    protected $db;
    protected $prefix;
    protected $dbname;

    public function __construct()
    {
        parent::__construct();

        $desei = $this->dbObj->sqlGet(
            "SELECT etchat_config_id, etchat_config_style FROM {$this->_prefix}etchat_config WHERE etchat_config_id = '1'"
        );

        $configFile = dirname(dirname(__DIR__)) . "/config.php";
        if (!file_exists($configFile)) {
            die("FEHLER: config.php nicht gefunden!");
        }
        include $configFile;

        $this->prefix = $prefix;
        $this->dbname = $database;

        try {
            $this->db = new PDO(
                "mysql:host={$sqlhost};dbname={$database};charset=utf8mb4",
                $sqluser,
                $sqlpass,
                [
                    PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
                    PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
                ]
            );
        } catch (PDOException $e) {
            die("FEHLER: Datenbankverbindung: " . $e->getMessage());
        }

        session_start();

        if ($_SESSION["etchat_" . $this->prefix . "user_priv"] !== "admin") {
            die("Zugriff verweigert!");
        }
		
		if (empty($_SESSION['etchat_' . $this->prefix . 'csrf_token'])) {
			$_SESSION['etchat_' . $this->prefix . 'csrf_token'] = bin2hex(random_bytes(16));
		}

		$csrfToken = $_SESSION['etchat_' . $this->prefix . 'csrf_token'];
		
		if ($_SERVER["REQUEST_METHOD"] === "POST") {
			
			    $token = $_POST['csrf_token'] ?? '';

					if (!hash_equals($_SESSION['etchat_' . $this->prefix . 'csrf_token'], $token)) {
						die('CSRF Token ungültig!');
					}
			
			$action = $_POST["action"] ?? "";

			switch ($action) {
				case "restore":
					if (isset($_POST["file"])) {
						$this->renderLoading(
							"Update wird installiert... Bitte einen Moment Geduld.",
							"Update Installation",
							$_POST["file"]
						);
						// zusätzlich:
						$method = 'get';
					}
					break;

				case "delete":
					if (isset($_POST["file"])) {
						$this->deleteBackupFile($_POST["file"]);
					}
					break;

				case "upload":
					$this->handleUpload();
					break;
			}

			exit();
		}

        if (isset($_GET["action"])) {
            switch ($_GET["action"]) {
                case "restoreRun":
                    if (isset($_GET["file"])) {
                        $this->restoreBackup($_GET["file"]);
                    }
                    break;
                case "downloadUpdate":
                    if (isset($_GET["version"])) {
                        // So wichtig: Version an die Methode weitergeben!
                        $this->downloadUpdate($_GET["version"]);
                    } else {
                        echo "<span style='color:red;'>Keine Versionsnummer angegeben.</span>";
                    }
                    break;
            }
            exit();
        }

        $updateFiles = $this->getUpdateFiles();
		$this->renderTemplate($updateFiles);
    }

    private function getAvailableUpdates($localVersion)
    {
        $updateIndexUrl =
            "https://powermen007.github.io/et-chat-mod-update/updates.json";
        $response = $this->fetchRemoteVersion($updateIndexUrl);

        if (!$response) {
            echo "<span style='color:red;'>Fehler beim Laden der Update-Liste.</span><br>";
            return [];
        }

        $files = json_decode($response, true);
        if (!is_array($files)) {
            echo "<span style='color:red;'>Update-Liste ist ungültig.</span><br>";
            return [];
        }

        $updates = [];

        foreach ($files as $file) {
            if (
                preg_match("/v(\d+\.\d+(?:\.\d+)?(?:\.\d+)?)/i", $file, $match)
            ) {
                $version = $match[1];
                if (version_compare($version, $localVersion, ">")) {
                    $updates[] = [
                        "version" => $version,
                        "file" => $file,
                    ];
                }
            }
        }

        usort(
            $updates,
            fn($a, $b) => version_compare($a["version"], $b["version"])
        );
        return $updates;
    }

    private function deleteBackupFile($filename)
    {
        $updateFolder = dirname(dirname(__DIR__)) . "/update";
        $filePath = $updateFolder . "/" . basename($filename);

        if (file_exists($filePath)) {
            unlink($filePath);
            header("Location: ?AdminUpDate");
            exit();
        } else {
            $this->renderResult("Datei nicht gefunden");
        }
    }

    private function restoreBackup($filename)
    {
        $updateFolder = dirname(dirname(__DIR__)) . "/update";
        $filePath = $updateFolder . "/" . basename($filename);

        if (!file_exists($filePath)) {
            $this->renderResult("Chat Update-Datei nicht gefunden");
            return;
        }

        $extension = pathinfo($filePath, PATHINFO_EXTENSION);

        if ($extension === "zip") {
            $result = $this->restoreFiles($filePath);

			$message = "Chat Update Fertiggestellt";

			if ($result['db']) {
				$message .= " (inkl. Datenbankänderungen)";
			}

			$this->renderResult($message, $result['files']);
        } else {
            $this->renderResult(
                "Ungültiger Dateityp, nur ZIP-Dateien werden akzeptiert"
            );
        }
    }

    private function restoreFiles($zipFile)
    {
		
		$changedFiles = [];
		$dbChanged = false;
		
        $zip = new ZipArchive();
        if ($zip->open($zipFile) !== true) {
            $this->renderResult("Fehler beim Öffnen der ZIP-Datei: $zipFile");
            return;
        }

        $basePath = dirname(dirname(__DIR__));
        $sqlFilePath = null;

        for ($i = 0; $i < $zip->numFiles; $i++) {
            $filename = $zip->getNameIndex($i);
            $targetPath = $basePath . "/" . $filename;
			
			// Nur echte Dateien sammeln (keine Ordner)
			if (substr($filename, -1) !== "/") {
				$changedFiles[] = $filename;
			}

            // Verzeichnisse überspringen
            if (substr($filename, -1) === "/") {
                if (!is_dir($targetPath)) {
                    mkdir($targetPath, 0777, true);
                }
                continue;
            }

            // Falls "install/mysql_db.sql"
            if (strtolower($filename) === "install/mysql_db.sql") {
				$sqlFilePath = $targetPath;
				$dbChanged = true;
			}

            // Zielverzeichnis erstellen, falls nötig
            $dir = dirname($targetPath);
            if (!is_dir($dir)) {
                mkdir($dir, 0777, true);
            }

            // Datei extrahieren
            $fileContent = $zip->getFromIndex($i);
            if ($fileContent !== false) {
                file_put_contents($targetPath, $fileContent);
            }
        }

        $zip->close();

        // SQL-Datei ggf. importieren
        if ($sqlFilePath && file_exists($sqlFilePath)) {
            $this->importAdditionalSQL($sqlFilePath);
        }
		
			return [
				'files' => $changedFiles,
				'db' => $dbChanged
			];
    }

    private function handleUpload()
    {
        if (!isset($_FILES["update_file"])) {
            $this->renderResult("Keine Datei hochgeladen");
            return;
        }

        $uploadedFile = $_FILES["update_file"];
        $updateFolder = dirname(dirname(__DIR__)) . "/update";

        $extension = pathinfo($uploadedFile["name"], PATHINFO_EXTENSION);
        if (strtolower($extension) !== "zip") {
            $this->renderResult("Nur .zip Dateien erlaubt");
            return;
        }

        $targetPath = $updateFolder . "/" . basename($uploadedFile["name"]);

        if (move_uploaded_file($uploadedFile["tmp_name"], $targetPath)) {
            header("Location: ?AdminUpDate");
            exit();
        } else {
            $this->renderResult("Fehler beim Hochladen");
        }
    }

    private function checkForNewVersion()
    {
        // Schritt 1: Lokale Version laden
        $localRaw = @file_get_contents(
            dirname(dirname(__DIR__)) . "/version.txt"
        );
        $localVersion = $this->extractVersion($localRaw);

        if (!$localVersion || $localVersion === "0.0.0.0") {
            echo "<span style='color: red;'>Lokale Version nicht erkennbar.</span><br>";
            return;
        }

        echo "<h3>Update Status</h3>";
        echo "<strong>Lokale Version:</strong> $localVersion<br>";

        // Erfolgsmeldung, falls Download war
        if (isset($_GET["msg"]) && $_GET["msg"] === "download_ok") {
            $v = htmlspecialchars($_GET["downloaded_version"] ?? "");
            echo "<div style='color: green;'><strong>Update-Version $v erfolgreich heruntergeladen.</strong></div><br>";
        }

        // Schritt 2: Verfügbare Updates ermitteln
        $updates = $this->getAvailableUpdates($localVersion);

        if (empty($updates)) {
            echo "<span style='color: green;'>Deine Version ist aktuell.</span>";
        } else {
            echo "<strong>Verfügbare Updates:</strong><br>";
            foreach ($updates as $update) {
                $ver = $update["version"];
                echo "Version: <strong>$ver</strong> ";
                echo "<a href='?AdminUpDate&action=downloadUpdate&version=$ver'>Herunterladen</a><br>";
            }
        }
    }

    private function extractVersion($text)
    {
        // Suche nach Versionsnummern mit mindestens zwei Segmenten, z.B. "1.9" oder "1.9.1.4"
        if (preg_match("/\b(\d+(?:\.\d+)+)\b/", $text, $match)) {
            return $match[1]; // Gefundene Version zurückgeben
        }
        return "0.0.0"; // Default, falls keine Version gefunden wurde
    }

    private function fetchRemoteVersion($url)
    {
        $ch = curl_init($url);
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
        curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true); // <-- Wichtig bei GitHub Pages
        curl_setopt($ch, CURLOPT_TIMEOUT, 5);
        curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false); // Optional bei Problemen mit SSL

        $response = curl_exec($ch);

        if (curl_errno($ch)) {
            $response = "";
        }

        curl_close($ch);
        return $response;
    }

    private function downloadUpdate($version)
    {
        $remoteUrlBase = "https://powermen007.github.io/et-chat-mod-update/";
        $downloadDir = dirname(dirname(__DIR__)) . "/update";
        $filename = "Update-chat-M.C.v$version.zip";
        $downloadUrl = $remoteUrlBase . $filename;
        $destination = $downloadDir . "/" . $filename;

        // Download mit cURL
        $ch = curl_init($downloadUrl);
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
        curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
        curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
        $data = curl_exec($ch);

        if (curl_errno($ch)) {
            echo "<span style='color: red;'>Fehler beim Herunterladen der Datei: " .
                curl_error($ch) .
                "</span>";
            curl_close($ch);
            return;
        }

        curl_close($ch);

        // Speicherverzeichnis prüfen
        if (!is_dir($downloadDir)) {
            mkdir($downloadDir, 0755, true);
        }

        // Datei speichern
        if (file_put_contents($destination, $data)) {
            header(
                "Location: ?AdminUpDate&msg=download_ok&downloaded_version=$version"
            );
            exit();
        } else {
            echo "<span style='color: red;'>Fehler beim Speichern der Datei.</span>";
            exit();
        }
    }

	private function renderTemplate(array $updateFiles): void
	{
		$tplPath = __DIR__ . '/../../styles/admin_tpl/indexUpdate.tpl.html';

		if (!file_exists($tplPath)) {
			die('Template nicht gefunden: ' . $tplPath);
		}

		$csrfToken = $_SESSION['etchat_' . $this->prefix . 'csrf_token'];

		include $tplPath;
	}

	private function getUpdateFiles(): array
	{
		$updateFolder = dirname(dirname(__DIR__)) . "/update";
		$result = [];

		if (is_dir($updateFolder)) {
			foreach (scandir($updateFolder, SCANDIR_SORT_DESCENDING) as $file) {
				if ($file === '.' || $file === '..') continue;

				$filePath = $updateFolder . '/' . $file;

				$result[] = [
					'name' => $file,
					'size' => filesize($filePath),
					'date' => date('d.m.Y H:i', filemtime($filePath)),
					'type' => pathinfo($filePath, PATHINFO_EXTENSION)
				];
			}
		}

		return $result;
	}

private function renderLoading(string $message, string $title, string $file = ''): void
{
    $tplPath = __DIR__ . '/../../styles/admin_tpl/admin_loading_screen.tpl.html';

    if (!file_exists($tplPath)) {
        die('Template nicht gefunden: ' . $tplPath);
    }

    $prefix = $this->prefix;
    $csrfToken = $_SESSION['etchat_' . $this->prefix . 'csrf_token'];
	 $action = 'restoreRun';

    $moduleName = $this->moduleName;
    $backLink   = './?' . $moduleName;

    include $tplPath;
    exit();
}

private function renderResult(string $message, array $files = []): void
{
    $tplPath = __DIR__ . '/../../styles/admin_tpl/createUpdate.tpl.html';

    if (!file_exists($tplPath)) {
        die('Template nicht gefunden: ' . $tplPath);
    }

    include $tplPath;
    exit();
}

private function importAdditionalSQL($sqlFile)
{
    $sql = file_get_contents($sqlFile);
    if ($sql === false) {
        $this->renderResult("Fehler beim Lesen von install/mysql_db.sql");
        return;
    }

    $prefix = $this->prefix;

    // Präfix einsetzen
    $sql = preg_replace_callback(
        '/\b(ALTER\s+TABLE|CREATE\s+TABLE|INSERT\s+INTO|UPDATE|DELETE\s+FROM)\s+`(etchat_[a-z0-9_]+)`/i',
        fn($m) => $m[1] . " `" . $prefix . $m[2] . "`",
        $sql
    );

    $this->db->exec("SET FOREIGN_KEY_CHECKS = 0");

    $statements = array_filter(
        array_map("trim", explode(";", $sql)),
        fn($st) => !empty($st) && !preg_match("/^\s*--/", $st)
    );

    foreach ($statements as $statement) {
        try {
            // CREATE TABLE IF NOT EXISTS einfügen
            if (preg_match('/^CREATE\s+TABLE\s+`([^`]+)`/i', $statement, $m)) {
                $table = $m[1];
                $statement = preg_replace('/^CREATE\s+TABLE/i', 'CREATE TABLE IF NOT EXISTS', $statement);
            }

            // ALTER TABLE prüfen, ob Spalten schon existieren
            if (preg_match('/^ALTER\s+TABLE\s+`([^`]+)`/i', $statement, $m)) {
                $table = $m[1];
                if (preg_match_all('/ADD\s+`([^`]+)`/i', $statement, $cols)) {
                    foreach ($cols[1] as $col) {
                        $stmt = $this->db->prepare("SHOW COLUMNS FROM `$table` LIKE ?");
                        $stmt->execute([$col]);
                        if ($stmt->rowCount() > 0) {
                            // Spalte existiert -> diese ADD-Zeile entfernen
                            $statement = preg_replace('/ADD\s+`' . preg_quote($col, '/') . '`[^,]*/i', '', $statement);
                        }
                    }
                    // Falls nach Entfernen alles leer ? überspringen
                    if (!preg_match('/ADD\s+`/i', $statement)) {
                        continue;
                    }
                }
            }

            // INSERT anpassen
            if (preg_match('/^INSERT\s+INTO/i', $statement)) {
                $statement = preg_replace('/^INSERT\s+INTO/i', 'INSERT IGNORE INTO', $statement);
            }

            $this->db->exec($statement);

        } catch (PDOException $e) {
            // Fehler nur loggen, nicht alles abbrechen
            error_log("SQL-Fehler: " . $e->getMessage() . " | Statement: " . $statement);
        }
    }

    $this->db->exec("SET FOREIGN_KEY_CHECKS = 1");
}

}