<?php

declare(strict_types=1);

namespace App\Framework\Tracing;

use App\Framework\Http\HttpMiddleware;
use App\Framework\Http\HttpRequest;
use App\Framework\Http\HttpResponse;
use App\Framework\Http\MiddlewareContext;
use App\Framework\Http\MiddlewarePriority;
use App\Framework\Http\MiddlewarePriorityAttribute;
use App\Framework\Http\Next;
use App\Framework\Http\Request;
use App\Framework\Http\RequestStateManager;
use App\Framework\Http\ResponseManipulator;
use App\Framework\Random\RandomGenerator;

#[MiddlewarePriorityAttribute(MiddlewarePriority::BUSINESS_LOGIC)]
final readonly class TracingMiddleware implements HttpMiddleware
{
    public function __construct(
        private RandomGenerator $randomGenerator,
        private TraceCollector $collector,
        private ResponseManipulator $responseManipulator
    ) {
    }

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

        // Start trace context
        $traceId = $this->extractTraceId($request);
        $traceContext = TraceContext::start($this->randomGenerator, $traceId);

        // Start main request span
        $span = $traceContext->startSpan(
            name: 'http.request',
            operation: 'http',
            tags: [
                'http.method' => $request->method->value,
                'http.path' => $request->path,
                'http.query' => http_build_query($request->queryParams),
                'http.user_agent' => $request->headers->getFirst('User-Agent') ?? 'unknown',
                'http.remote_addr' => $request->server->getRemoteAddr()->value,
                'http.client_ip' => $request->server->getClientIp()->value,
                'http.is_https' => $request->server->isHttps(),
                'http.referer' => $request->server->getReferer(),
            ]
        );

        // Add trace ID to request headers
        $updatedRequest = new HttpRequest(
            method: $request->method,
            headers: $request->headers->with('X-Trace-Id', $traceContext->getTraceId()),
            body: $request->body,
            path: $request->path,
            queryParams: $request->queryParams,
            files: $request->files,
            cookies: $request->cookies,
            server: $request->server,
            id: $request->id,
            parsedBody: $request->parsedBody
        );
        $context = new MiddlewareContext($updatedRequest, $context->response);

        try {
            $context = $next($context);
            $response = $context->response;

            if ($response !== null) {
                // Add response information
                $span->addTags([
                    'http.status_code' => $response->status->value,
                    'http.response_size' => strlen($response->body),
                ]);

                // Set span status based on response
                if ($response->status->value >= 400) {
                    $span->setStatus(SpanStatus::ERROR, "HTTP {$response->status->value}");
                } else {
                    $span->setStatus(SpanStatus::OK);
                }

                // Add trace ID to response headers
                $updatedResponse = $this->responseManipulator->withHeader($response, 'X-Trace-Id', $traceContext->getTraceId());
                /*$updatedResponse = new HttpResponse(
                    status: $response->status,
                    headers: $response->headers->with('X-Trace-Id', $traceContext->getTraceId()),
                    body: $response->body
                );*/
                $context = new MiddlewareContext($context->request, $updatedResponse);
            }

            return $context;

        } catch (\Throwable $error) {
            $span->recordError($error);
            $span->setStatus(SpanStatus::ERROR, $error->getMessage());

            throw $error;

        } finally {
            $traceContext->finishSpan($span);

            // Collect trace data
            $this->collector->collect($traceContext);

            // Clear context
            TraceContext::clear();
        }
    }

    private function extractTraceId(Request $request): ?string
    {
        // Check for existing trace ID in headers
        $traceId = $request->headers->getFirst('X-Trace-Id');
        if (! empty($traceId)) {
            return $traceId;
        }

        // Check for distributed tracing headers
        $traceParent = $request->headers->getFirst('traceparent');
        if (! empty($traceParent)) {
            // Parse W3C trace context format: version-traceid-spanid-flags
            $parts = explode('-', $traceParent);
            if (count($parts) >= 2) {
                return $parts[1];
            }
        }

        return null;
    }
}
