<?php

declare(strict_types=1);

namespace App\Framework\Core;

use App\Framework\Config\TypedConfiguration;
use App\Framework\Core\Events\AfterEmitResponse;
use App\Framework\Core\Events\AfterHandleRequest;
use App\Framework\Core\Events\ApplicationBooted;
use App\Framework\Core\Events\BeforeEmitResponse;
use App\Framework\Core\Events\BeforeHandleRequest;
use App\Framework\Core\Events\EventDispatcherInterface;
use App\Framework\Core\ValueObjects\Duration;
use App\Framework\DI\Container;
use App\Framework\Http\MiddlewareManagerInterface;
use App\Framework\Http\Request;
use App\Framework\Http\Response;
use App\Framework\Http\ResponseEmitter;
use App\Framework\Performance\Contracts\PerformanceCollectorInterface;
use App\Framework\Performance\PerformanceCategory;
use App\Framework\Router\HttpRouter;
use DateTimeImmutable;

final readonly class Application implements ApplicationInterface
{
    private MiddlewareManagerInterface $middlewareManager;

    private EventDispatcherInterface $eventDispatcher;

    private PerformanceCollectorInterface $performanceCollector;

    public function __construct(
        private Container       $container,
        private ResponseEmitter $responseEmitter,
        private TypedConfiguration   $config,
        ?MiddlewareManagerInterface      $middlewareManager = null,
        ?EventDispatcherInterface        $eventDispatcher = null,
        ?PerformanceCollectorInterface   $performanceCollector = null,
    ) {
        // Dependencies optional injizieren oder aus Container holen
        $this->middlewareManager = $middlewareManager ?? $this->container->get(MiddlewareManagerInterface::class);
        $this->eventDispatcher = $eventDispatcher ?? $this->container->get(EventDispatcherInterface::class);
        $this->performanceCollector = $performanceCollector ?? $this->container->get(PerformanceCollectorInterface::class);
    }

    /**
     * Führt die Anwendung aus
     */
    public function run(): void
    {
        $this->boot();
        $response = $this->handleRequest($this->container->get(Request::class));
        $this->emitResponse($response);
    }

    /**
     * Bootstrap der Anwendung
     */
    private function boot(): void
    {
        // ApplicationBooted-Event dispatchen
        $environment = $this->config->app->environment;
        $version = $this->config->app->version;

        $bootEvent = new ApplicationBooted(
            bootTime: new DateTimeImmutable(),
            environment: $environment,
            version: $version
        );

        $this->event($bootEvent);

        // Sicherstellen, dass ein Router registriert wurde
        if (! $this->container->has(HttpRouter::class)) {
            throw new \RuntimeException('Kritischer Fehler: Router wurde nicht initialisiert');
        }
    }

    /**
     * Verarbeitet den HTTP-Request
     */
    private function handleRequest(Request $request): Response
    {
        $this->event(new BeforeHandleRequest(
            request: $request,
            context: [
                'route' => $request->path,
                'method' => $request->method->value,
                'user_agent' => $request->server->getUserAgent()?->toString(),
                'client_ip' => (string) $request->server->getClientIp(),
            ]
        ));

        $response = $this->performanceCollector->measure(
            'handle_request',
            PerformanceCategory::SYSTEM,
            function () use ($request) {
                return $this->middlewareManager->chain->handle($request);
            }
        );

        // Duration aus PerformanceCollector abrufen
        $requestMetrics = $this->performanceCollector->getMetrics(PerformanceCategory::SYSTEM);
        $processingTime = Duration::zero();

        if (isset($requestMetrics['handle_request'])) {
            $measurements = $requestMetrics['handle_request']->getMeasurements();
            $latestMeasurement = $measurements->getLast();
            if ($latestMeasurement !== null) {
                $processingTime = $latestMeasurement->getDuration();
            }
        }

        $this->event(new AfterHandleRequest(
            request: $request,
            response: $response,
            processingTime: $processingTime,
            context: [
                'status_code' => $response->status->value,
                'content_length' => strlen($response->body),
                'memory_peak' => memory_get_peak_usage(true),
            ]
        ));

        return $response;
    }

    /**
     * Gibt die HTTP-Response aus
     */
    private function emitResponse(Response $response): void
    {
        $request = $this->container->get(Request::class);

        $this->event(new BeforeEmitResponse(
            request: $request,
            response: $response,
            context: [
                'content_type' => $response->headers->getFirst('Content-Type', 'text/html'),
                'cache_control' => $response->headers->getFirst('Cache-Control', 'no-cache'),
            ]
        ));

        $this->responseEmitter->emit($response);

        // Gesamtzeit aus PerformanceCollector abrufen
        $systemMetrics = $this->performanceCollector->getMetrics(PerformanceCategory::SYSTEM);
        $totalTime = Duration::zero();

        if (isset($systemMetrics['handle_request'])) {
            $measurements = $systemMetrics['handle_request']->getMeasurements();
            $latestMeasurement = $measurements->getLast();
            if ($latestMeasurement !== null) {
                $totalTime = $latestMeasurement->getDuration();
            }
        }

        $this->event(new AfterEmitResponse(
            request: $request,
            response: $response,
            totalProcessingTime: $totalTime,
            context: [
                'bytes_sent' => strlen($response->body),
                'final_status' => $response->status->value,
            ]
        ));
    }

    private function event(object $event): void
    {
        $this->eventDispatcher->dispatch($event);
    }
}
