<?php

declare(strict_types=1);

namespace App\Framework\Performance\Middleware;

use App\Framework\Core\ValueObjects\Byte;
use App\Framework\Http\HttpMiddleware;
use App\Framework\Http\MiddlewareContext;
use App\Framework\Http\MiddlewarePriority;
use App\Framework\Http\MiddlewarePriorityAttribute;
use App\Framework\Http\Next;
use App\Framework\Http\RequestStateManager;
use App\Framework\Performance\Contracts\PerformanceCollectorInterface;
use App\Framework\Performance\MemoryMonitor;
use App\Framework\Performance\PerformanceCategory;
use App\Framework\Performance\PerformanceConfig;

#[MiddlewarePriorityAttribute(MiddlewarePriority::FIRST)]
final readonly class RequestPerformanceMiddleware implements HttpMiddleware
{
    public function __construct(
        private PerformanceCollectorInterface $collector,
        private PerformanceConfig $config
    ) {
    }

    public function __invoke(MiddlewareContext $context, Next $next, RequestStateManager $stateManager): MiddlewareContext
    {
        if (! $this->config->isTrackingEnabled(PerformanceCategory::SYSTEM)) {
            return $next($context);
        }

        $request = $context->request;
        $path = $request->path ?? '/';

        if ($this->config->shouldExcludePath($path)) {
            return $next($context);
        }

        $requestKey = 'http_request';
        $performanceContext = [
            'method' => $request->method,
            'path' => $path,
            'user_agent' => $request->headers->getFirst('User-Agent', ''),
            'ip' => $request->server->getClientIp()->value,
        ];

        $this->collector->startTiming($requestKey, PerformanceCategory::SYSTEM, $performanceContext);

        // Initialize memory monitoring
        $memoryMonitor = new MemoryMonitor();

        // Memory usage before request
        $memoryBefore = $memoryMonitor->getCurrentMemory();
        $this->collector->recordMetric(
            'memory_before_request',
            PerformanceCategory::SYSTEM,
            $memoryBefore->toBytes(),
            array_merge($performanceContext, [
                'memory_human' => $memoryBefore->toHumanReadable(),
                'memory_percentage' => $memoryMonitor->getMemoryUsagePercentage(),
            ])
        );

        try {
            $result = $next($context);

            // Record successful request
            $this->collector->increment('requests_total', PerformanceCategory::SYSTEM, 1, $performanceContext);
            $this->collector->increment('requests_success', PerformanceCategory::SYSTEM, 1, $performanceContext);

            return $result;
        } catch (\Throwable $e) {
            // Record failed request
            $this->collector->increment('requests_total', PerformanceCategory::SYSTEM, 1, $performanceContext);
            $this->collector->increment('requests_failed', PerformanceCategory::SYSTEM, 1, $performanceContext);
            $this->collector->recordMetric(
                'request_error',
                PerformanceCategory::SYSTEM,
                1,
                array_merge($performanceContext, ['error' => $e::class])
            );

            throw $e;
        } finally {
            $this->collector->endTiming($requestKey);

            // Memory monitoring after request
            $memoryAfter = $memoryMonitor->getCurrentMemory();
            $memoryPeak = $memoryMonitor->getPeakMemory();
            $memoryDiff = $memoryAfter->greaterThan($memoryBefore)
                ? $memoryAfter->subtract($memoryBefore)
                : Byte::zero();

            $this->collector->recordMetric(
                'memory_after_request',
                PerformanceCategory::SYSTEM,
                $memoryAfter->toBytes(),
                array_merge($performanceContext, [
                    'memory_human' => $memoryAfter->toHumanReadable(),
                    'memory_percentage' => $memoryMonitor->getMemoryUsagePercentage(),
                ])
            );

            if ($memoryDiff->isNotEmpty()) {
                $this->collector->recordMetric(
                    'memory_usage_request',
                    PerformanceCategory::SYSTEM,
                    $memoryDiff->toBytes(),
                    array_merge($performanceContext, [
                        'memory_diff_human' => $memoryDiff->toHumanReadable(),
                    ])
                );
            }

            // Peak memory
            $this->collector->recordMetric(
                'memory_peak',
                PerformanceCategory::SYSTEM,
                $memoryPeak->toBytes(),
                array_merge($performanceContext, [
                    'memory_peak_human' => $memoryPeak->toHumanReadable(),
                    'is_approaching_limit' => $memoryMonitor->isMemoryLimitApproaching(),
                ])
            );
        }
    }
}
