<?php
declare(strict_types=1);

namespace App\Framework\Http;

use App\Framework\Http\Responses\WebSocketResponse;
use App\Framework\Router\Result\WebSocketResult;

/**
 * WebSocket-Server für die Verarbeitung von WebSocket-Verbindungen
 */
final class WebSocketServer
{
    private array $connections = [];

    public function handleUpgrade(WebSocketResponse $response): void
    {
        $webSocketResult = $response->getWebSocketResult();

        // Socket-Stream abrufen (vereinfacht für Demonstration)
        $socket = $this->getCurrentSocket();

        if (!$socket) {
            return;
        }

        // Verbindung erstellen
        $connectionId = uniqid('ws_', true);
        $connection = new WebSocketConnection($connectionId, $socket);
        $this->connections[$connectionId] = $connection;

        // onConnect-Handler aufrufen
        if ($webSocketResult->getOnConnect()) {
            try {
                call_user_func($webSocketResult->getOnConnect(), $connection);
            } catch (\Throwable $e) {
                $this->handleError($connection, $e, $webSocketResult);
                return;
            }
        }

        // Message-Loop starten
        $this->messageLoop($connection, $webSocketResult);
    }

    /**
     * Hauptschleife für die Nachrichtenverarbeitung
     */
    private function messageLoop(WebSocketConnection $connection, WebSocketResult $result): void
    {
        $socket = $this->getSocketFromConnection($connection);
        $lastPing = time();
        $pingInterval = $result->getPingInterval();

        while ($connection->isConnected()) {
            // Socket auf Lesbarkeit prüfen
            $read = [$socket];
            $write = [];
            $except = [];

            $ready = stream_select($read, $write, $except, 1); // 1 Sekunde Timeout

            if ($ready === false) {
                break; // Fehler
            }

            if ($ready > 0) {
                // Nachricht lesen
                $message = $this->readMessage($socket, $result->getMaxMessageSize());

                if ($message === false) {
                    break; // Verbindung geschlossen oder Fehler
                }

                if ($message !== null) {
                    // onMessage-Handler aufrufen
                    if ($result->getOnMessage()) {
                        try {
                            call_user_func($result->getOnMessage(), $connection, $message);
                        } catch (\Throwable $e) {
                            $this->handleError($connection, $e, $result);
                            break;
                        }
                    }
                }
            }

            // Ping senden, falls nötig
            if (time() - $lastPing >= $pingInterval) {
                $connection->ping();
                $lastPing = time();
            }
        }

        // Verbindung schließen
        $this->closeConnection($connection, $result);
    }

    /**
     * Liest eine WebSocket-Nachricht
     */
    private function readMessage($socket, int $maxSize): string|false|null
    {
        // Vereinfachte WebSocket-Frame-Dekodierung
        $header = fread($socket, 2);
        if (strlen($header) !== 2) {
            return false;
        }

        $firstByte = ord($header[0]);
        $secondByte = ord($header[1]);

        $opcode = $firstByte & 0x0F;
        $masked = ($secondByte & 0x80) === 0x80;
        $payloadLength = $secondByte & 0x7F;

        // Erweiterte Payload-Länge lesen
        if ($payloadLength === 126) {
            $extLength = fread($socket, 2);
            if (strlen($extLength) !== 2) {
                return false;
            }
            $payloadLength = unpack('n', $extLength)[1];
        } elseif ($payloadLength === 127) {
            $extLength = fread($socket, 8);
            if (strlen($extLength) !== 8) {
                return false;
            }
            $payloadLength = unpack('J', $extLength)[1];
        }

        // Größencheck
        if ($payloadLength > $maxSize) {
            return false;
        }

        // Maske lesen (falls vorhanden)
        $maskingKey = '';
        if ($masked) {
            $maskingKey = fread($socket, 4);
            if (strlen($maskingKey) !== 4) {
                return false;
            }
        }

        // Payload lesen
        $payload = '';
        if ($payloadLength > 0) {
            $payload = fread($socket, $payloadLength);
            if (strlen($payload) !== $payloadLength) {
                return false;
            }

            // Demaskierung
            if ($masked) {
                for ($i = 0; $i < $payloadLength; $i++) {
                    $payload[$i] = chr(ord($payload[$i]) ^ ord($maskingKey[$i % 4]));
                }
            }
        }

        // Frame-Typ verarbeiten
        switch ($opcode) {
            case 0x1: // Text
                return $payload;
            case 0x2: // Binary
                return $payload;
            case 0x8: // Close
                return false;
            case 0x9: // Ping
                // Pong senden
                $this->sendPong($socket, $payload);
                return null;
            case 0xA: // Pong
                return null;
            default:
                return null;
        }
    }

    private function sendPong($socket, string $data): void
    {
        $frame = chr(0x8A) . chr(strlen($data)) . $data;
        fwrite($socket, $frame);
    }

    private function handleError(WebSocketConnection $connection, \Throwable $error, WebSocketResult $result): void
    {
        if ($result->getOnError()) {
            try {
                call_user_func($result->getOnError(), $connection, $error);
            } catch (\Throwable $e) {
                // Fehler im Error-Handler ignorieren
            }
        }
    }

    private function closeConnection(WebSocketConnection $connection, WebSocketResult $result): void
    {
        if ($result->getOnClose()) {
            try {
                call_user_func($result->getOnClose(), $connection, 1000, 'Normal closure');
            } catch (\Throwable $e) {
                // Fehler im Close-Handler ignorieren
            }
        }

        $connection->disconnect();
        unset($this->connections[$connection->getId()]);
    }

    /**
     * Hilfsmethoden (vereinfacht für Demo)
     */
    private function getCurrentSocket()
    {
        // In einer echten Implementierung würdest du hier den aktuellen Socket abrufen
        // Für PHP-FPM/Apache ist das komplizierter, da du keinen direkten Socket-Zugriff hast
        return null;
    }

    private function getSocketFromConnection(WebSocketConnection $connection)
    {
        // Socket aus der Verbindung extrahieren
        return null;
    }
}
