<?php

declare(strict_types=1);

namespace App\Framework\Http\Middlewares;

use App\Framework\Core\PathProvider;
use App\Framework\DateTime\Clock;
use App\Framework\Filesystem\Storage;
use App\Framework\Http\Headers;
use App\Framework\Http\HttpMiddleware;
use App\Framework\Http\HttpResponse;
use App\Framework\Http\MiddlewareContext;
use App\Framework\Http\Next;
use App\Framework\Http\RequestStateManager;
use App\Framework\Http\Status;

class ServeStaticFilesMiddleware implements HttpMiddleware
{
    /** @var array<string> */
    private array $allowedExtensions = [
        'jpg', 'jpeg', 'png', 'gif', 'webp', 'avif', 'svg',
        'css', 'js', 'woff', 'woff2', 'ttf', 'eot',
        'pdf', 'ico', 'xml', 'json',
    ];

    /** @var array<string, string> */
    private array $mimeTypes = [
        'jpg' => 'image/jpeg',
        'jpeg' => 'image/jpeg',
        'png' => 'image/png',
        'gif' => 'image/gif',
        'webp' => 'image/webp',
        'avif' => 'image/avif',
        'svg' => 'image/svg+xml',
        'css' => 'text/css',
        'js' => 'application/javascript',
        'woff' => 'font/woff',
        'woff2' => 'font/woff2',
        'ttf' => 'font/ttf',
        'eot' => 'application/vnd.ms-fontobject',
        'pdf' => 'application/pdf',
        'ico' => 'image/x-icon',
        'xml' => 'application/xml',
        'json' => 'application/json',
    ];

    public function __construct(
        private readonly PathProvider $pathProvider,
        private readonly Clock $clock,
        private readonly Storage $storage,
        private readonly string $mediaPrefix = '/media'
    ) {
    }

    public function __invoke(MiddlewareContext $context, Next $next, RequestStateManager $stateManager): MiddlewareContext
    {
        $request = $context->request;
        $path = $request->path;

        // Prüfen ob es sich um eine Media-Anfrage handelt
        if (str_starts_with($path, $this->mediaPrefix)) {
            $filePath = substr($path, strlen($this->mediaPrefix));
            $response = $this->serveStaticFile($filePath);

            return new MiddlewareContext($request, $response);
        }

        return $next($context);
    }

    private function serveStaticFile(string $filePath): HttpResponse
    {
        // Pfad bereinigen (verhindert Directory Traversal Angriffe)
        $filePath = $this->sanitizePath($filePath);

        // Dateiendung prüfen
        $extension = strtolower(pathinfo($filePath, PATHINFO_EXTENSION));
        if (! in_array($extension, $this->allowedExtensions)) {
            return $this->notFound();
        }

        // Vollständigen Dateipfad konstruieren
        $fullPath = $this->pathProvider->resolvePath('storage' . $filePath);

        // Prüfen ob Datei existiert
        if (! $this->storage->exists($fullPath)) {
            return $this->notFound();
        }

        // MIME-Typ ermitteln
        $mimeType = $this->mimeTypes[$extension] ?? 'application/octet-stream';

        // Storage verwenden
        $content = $this->storage->get($fullPath);

        // Cache-Control Header basierend auf Dateityp setzen
        $cacheControl = $this->getCacheControlHeader($extension);

        // ETag für Caching generieren
        $etag = '"' . md5($content) . '"';

        // Last-Modified Header mit Clock
        $lastModified = $this->clock->now()
            ->format('D, d M Y H:i:s') . ' GMT';

        // Headers zusammenstellen
        $headers = new Headers([
            'Content-Type' => $mimeType,
            'Content-Length' => (string) strlen($content),
            'Cache-Control' => $cacheControl,
            'ETag' => $etag,
            'Last-Modified' => $lastModified,
        ]);

        return new HttpResponse(
            Status::OK,
            $headers,
            $content
        );
    }

    private function sanitizePath(string $path): string
    {
        // Doppelte Slashes entfernen
        $path = preg_replace('#/+#', '/', $path) ?? $path;

        // Führende/nachfolgende Slashes entfernen
        $path = trim($path, '/');

        // Sicherstellen, dass kein '..' enthalten ist (verhindert Directory Traversal)
        $parts = [];
        foreach (explode('/', $path) as $part) {
            if ($part === '.') {
                continue;
            }

            if ($part === '..') {
                array_pop($parts);
            } else {
                $parts[] = $part;
            }
        }

        return '/' . implode('/', $parts);
    }

    private function getCacheControlHeader(string $extension): string
    {
        // Bilder und Fonts können länger gecacht werden
        $imageFontExtensions = ['jpg', 'jpeg', 'png', 'gif', 'webp', 'avif', 'svg', 'woff', 'woff2', 'ttf', 'eot'];

        if (in_array($extension, $imageFontExtensions)) {
            return 'public, max-age=31536000'; // 1 Jahr
        }

        // CSS und JS
        if (in_array($extension, ['css', 'js'])) {
            return 'public, max-age=604800'; // 1 Woche
        }

        // Sonstige Dateien
        return 'public, max-age=86400'; // 1 Tag
    }

    private function notFound(): HttpResponse
    {
        return new HttpResponse(
            Status::NOT_FOUND,
            new Headers(['Content-Type' => 'text/plain']),
            'Datei nicht gefunden'
        );
    }
}
