<?php

declare(strict_types=1);

namespace App\Application\Middleware;

use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface as Request;
use Psr\Http\Server\MiddlewareInterface as Middleware;
use Psr\Http\Server\RequestHandlerInterface as RequestHandler;
use Psr\Log\LoggerInterface;
use Psr\Http\Message\ResponseFactoryInterface; // Wichtig: Für die ResponseFactory
use Firebase\JWT\JWT; // Für JWT-Operationen
use Firebase\JWT\Key; // Für JWT-Schlüssel

// Importiere spezifische JWT-Exceptions für bessere Fehlerbehandlung
use Firebase\JWT\SignatureInvalidException;
use Firebase\JWT\ExpiredException;
use Firebase\JWT\BeforeValidException;
use UnexpectedValueException;

use Slim\Exception\HttpUnauthorizedException;

class MCPSessionMiddleware implements Middleware
{
    private LoggerInterface $logger;
    private ResponseFactoryInterface $responseFactory; // Injizierte ResponseFactory
    private string $jwtSecretKey; // JWT Secret Key als Property
    private string $jwtAlgorithm; // JWT Algorithm als Property

    // In src/Application/Middleware/MCPSessionMiddleware.php
    public function __construct(
        LoggerInterface $logger,
        ResponseFactoryInterface $responseFactory, // <- Dies ist der 2. Parameter
        string $jwtSecretKey,
        string $jwtAlgorithm = 'HS256',
        private array $options = []
    ) {
        $this->logger = $logger;
        $this->responseFactory = $responseFactory;
        $this->jwtSecretKey = $jwtSecretKey;
        $this->jwtAlgorithm = $jwtAlgorithm;

        // Standardoptionen mit übergebenen Optionen mergen
        $this->options = array_merge([
            "prefix" => "",
            "ignore" => []
        ], $options);
    }

    /**
     * {@inheritdoc}
     */
    public function process(Request $request, RequestHandler $handler): ResponseInterface
    {
        // Session-Start: Hier muss sorgfältig abgewogen werden.
        // Wenn du nur Token-basierte Authentifizierung machst, brauchst du keine PHP-Sessions.
        // Wenn du Sessions UND Tokens verwendest, dann ist session_start() hier in Ordnung.
        // Wenn du SlimSession\Helper verwendest, sollte die SlimSessionMiddleware dies handhaben.
        // Falls keine PHP-Sessions benötigt werden, diese Zeile entfernen.
        //session_start();
        $session = new \SlimSession\Helper(); // Diese Instanz ist oft nicht nötig, wenn session_start() global ist
        
        
        // $this->logger->debug('MCPSessionMiddleware received request.', [
        //     'method' => $request->getMethod(),
        //     'path' => $request->getUri()->getPath(),
        //     'headers' => $request->getHeaders() // Hier alle Header loggen!
        // ]);
        
        // Pfad normalisieren und Präfix entfernen
        $path = $request->getUri()->getPath();
        $route = str_replace($this->options['prefix'], '', $path);

        // Prüfen, ob die Route ignoriert werden soll (für Authentifizierung/Sitzung)
        if ($this->isRouteIgnored($route)) {
            //$this->logger->info('Route ignored for authentication check.', ['path' => $path]);
            
            return $handler->handle($request); // Anfrage direkt weiterleiten
        }
        else {
            // --- TOKEN-VERIFIZIERUNG ---
            //$this->logger->debug('Attempting token verification for protected route.', ['path' => $path]);
            
            $token = $this->getTokenFromRequest($request);
            //$this->logger->debug('token',['token' => $token]);
            
            if(!$token) {
                //return $this->createUnauthorizedResponse('Unauthorized: Token missing.');
            }

            //return $handler->handle($request);


            try {
                // Token verifizieren
                $decoded = $this->verifyToken($token  ?? '');

                // Optional: Token-Daten zur Request hinzufügen
                $request = $request->withAttribute('jwt_payload', $decoded);

                // Weiterleiten
                return $handler->handle($request);

            } catch (\Exception $e) {
                // Token ungültig
                //return $this->createUnauthorizedResponse('Unauthorized: ' . $e->getMessage());
                return $handler->handle($request);
            }
            
        }
    }

    /**
     * Extrahiert den Token aus dem Authorization-Header.
     */
    private function getTokenFromRequest(Request $request): ?string
    {
        $header = $request->getHeaderLine('Authorization');
        //$this->logger->debug('header',['header' => $header]);
        //$this->logger->debug('auth',['auth' => $_SERVER['HTTP_AUTHORIZATION']]);
        
        if (preg_match('/Bearer\s(\S+)/', $header, $matches)) {
            return $matches[1];
        }
        return null;
    }

    /**
     * Verifiziert den JWT-Token.
     *
     * @param string $token
     * @return object Der dekodierte Token-Payload
     * @throws \Exception Wenn der Token ungültig ist
     */
    private function verifyToken(string $token): object
    {
        try {
            // Dekodieren und Verifizieren des Tokens mit dem injizierten Schlüssel und Algorithmus
            $decoded = JWT::decode($token, new Key($this->jwtSecretKey, $this->jwtAlgorithm));
            return $decoded;

        } catch (SignatureInvalidException $e) {
            throw new \Exception('Invalid token signature.', 0, $e);
        } catch (ExpiredException $e) {
            throw new \Exception('Token has expired.', 0, $e);
        } catch (BeforeValidException $e) {
            throw new \Exception('Token not yet valid.', 0, $e);
        } catch (UnexpectedValueException $e) {
            throw new \Exception('Invalid token format or claims.', 0, $e);
        } catch (\Throwable $e) { // Fängt alle anderen unbehandelten Fehler ab
            throw new \Exception('Token verification error: ' . $e->getMessage(), 0, $e);
        }
    }

    /**
     * Prüft, ob die aktuelle Route auf der Ignore-Liste steht.
     * Funktioniert ohne RouteContext — läuft vor dem Routing-Middleware.
     * Platzhalter wie {id} werden per Regex als [^/]+ behandelt.
     */
    private function isRouteIgnored(string $route): bool
    {
        foreach ($this->options['ignore'] as $ignorePattern) {
            // Literal-Teile des Musters quoten, {placeholder} → [^/]+
            $parts = preg_split('/\{[^}]+\}/', $ignorePattern);
            $quotedParts = array_map(fn(string $p) => preg_quote($p, '#'), $parts);
            $pattern = '#^' . implode('[^/]+', $quotedParts) . '$#';

            if (preg_match($pattern, $route)) {
                return true;
            }
        }
        return false;
    }

    /**
     * Ermittelt die IP-Adresse des Clients.
     */
    private function getClientIpAddress(Request $request): ?string
    {
        // 1. Am häufigsten (Direkter Client oder letzter Proxy/Load Balancer)
        $serverParams = $request->getServerParams();
        if (isset($serverParams['REMOTE_ADDR'])) {
            $clientIp = $serverParams['REMOTE_ADDR'];
            if (filter_var($clientIp, FILTER_VALIDATE_IP)) {
                return $clientIp;
            }
        }

        // 2. WICHTIG: Wenn du einen Proxy/Load Balancer verwendest (z.B. Nginx, Cloudflare, AWS ELB)
        // Die Reihenfolge der Header ist wichtig, da X-Forwarded-For oft die Client-IP als erstes enthält.
        $trustedHeaders = [
            'X-Forwarded-For',  // Standard für viele Proxys, kann Komma-getrennt sein
            'CF-Connecting-IP', // Cloudflare
            'X-Real-Ip',        // Nginx
            'Client-Ip',        // Gelegentlich verwendet
        ];

        foreach ($trustedHeaders as $headerName) {
            if ($request->hasHeader($headerName)) {
                $headerValue = $request->getHeaderLine($headerName);
                // Bei X-Forwarded-For die erste IP nehmen (gilt als die des ursprünglichen Clients)
                if ($headerName === 'X-Forwarded-For') {
                    $ips = explode(',', $headerValue);
                    $ip = trim($ips[0]);
                } else {
                    $ip = $headerValue;
                }

                if (filter_var($ip, FILTER_VALIDATE_IP)) {
                    return $ip;
                }
            }
        }

        return null; // Keine gültige IP gefunden
    }

    /**
     * Erstellt eine standardisierte 401 Unauthorized Response.
     */
    private function createUnauthorizedResponse(string $message, ?\Throwable $previous = null): ResponseInterface
    {
        // Wirf eine Slim-spezifische Exception, wenn du möchtest,
        // dass die ErrorMiddleware dies verarbeitet.
        // throw new HttpUnauthorizedException($request, $message, $previous);

        $response = $this->responseFactory->createResponse(401);
        $response->getBody()->write(json_encode(['message' => $message]));
        return $response->withHeader('Content-Type', 'application/json');
    }
}