<?php

declare(strict_types=1);

namespace App\Framework\ErrorReporting\Processors;

use App\Framework\Core\ValueObjects\Duration;
use App\Framework\ErrorReporting\ErrorReport;

/**
 * Processor that enriches error reports with HTTP request context
 */
final readonly class RequestContextProcessor
{
    public function __invoke(ErrorReport $report): ErrorReport
    {
        // If already has request context, don't override
        if ($report->route || $report->method) {
            return $report;
        }

        // Extract from $_SERVER if available
        $method = $_SERVER['REQUEST_METHOD'] ?? null;
        $uri = $_SERVER['REQUEST_URI'] ?? null;
        $userAgent = $_SERVER['HTTP_USER_AGENT'] ?? null;
        $ipAddress = $this->getClientIpAddress();
        $requestId = $_SERVER['HTTP_X_REQUEST_ID'] ?? null;

        // Get request data (sanitized)
        $requestData = $this->getRequestData();

        // Calculate execution time if request start time is available
        $executionTime = null;
        if (isset($_SERVER['REQUEST_TIME_FLOAT'])) {
            $startTime = $_SERVER['REQUEST_TIME_FLOAT'];
            $currentTime = microtime(true);
            $executionTime = Duration::fromSeconds($currentTime - $startTime);
        }

        // Get memory usage
        $memoryUsage = memory_get_usage(true);

        $enrichedReport = $report;

        if ($method && $uri) {
            $enrichedReport = $enrichedReport->withRequest(
                method: $method,
                route: $this->normalizeRoute($uri),
                requestId: $requestId,
                userAgent: $userAgent,
                ipAddress: $ipAddress,
                requestData: $requestData
            );
        }

        if ($executionTime || $memoryUsage) {
            $enrichedReport = $enrichedReport->withPerformance(
                executionTime: $executionTime ?? Duration::zero(),
                memoryUsage: $memoryUsage
            );
        }

        // Add request-specific tags
        $tags = [];
        if ($method) {
            $tags[] = "method:{$method}";
        }
        if ($this->isApiRequest($uri)) {
            $tags[] = 'api';
        }
        if ($this->isAjaxRequest()) {
            $tags[] = 'ajax';
        }

        if (! empty($tags)) {
            $enrichedReport = $enrichedReport->withTags($tags);
        }

        return $enrichedReport;
    }

    private function getClientIpAddress(): ?string
    {
        // Check for various headers that might contain the real IP
        $headers = [
            'HTTP_X_FORWARDED_FOR',
            'HTTP_X_REAL_IP',
            'HTTP_CLIENT_IP',
            'HTTP_X_FORWARDED',
            'HTTP_FORWARDED_FOR',
            'HTTP_FORWARDED',
            'REMOTE_ADDR',
        ];

        foreach ($headers as $header) {
            if (! empty($_SERVER[$header])) {
                $ip = $_SERVER[$header];

                // Handle comma-separated list (X-Forwarded-For)
                if (str_contains($ip, ',')) {
                    $ip = trim(explode(',', $ip)[0]);
                }

                // Validate IP address
                if (filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE)) {
                    return $ip;
                }
            }
        }

        return $_SERVER['REMOTE_ADDR'] ?? null;
    }

    private function getRequestData(): ?array
    {
        $data = [];

        // GET parameters (limit and sanitize)
        if (! empty($_GET)) {
            $data['get'] = $this->sanitizeData($_GET);
        }

        // POST parameters (limit and sanitize)
        if (! empty($_POST)) {
            $data['post'] = $this->sanitizeData($_POST);
        }

        // JSON body (if present)
        $contentType = $_SERVER['CONTENT_TYPE'] ?? '';
        if (str_contains($contentType, 'application/json')) {
            $input = file_get_contents('php://input');
            if ($input) {
                $jsonData = json_decode($input, true);
                if (json_last_error() === JSON_ERROR_NONE) {
                    $data['json'] = $this->sanitizeData($jsonData);
                }
            }
        }

        return ! empty($data) ? $data : null;
    }

    private function sanitizeData(mixed $data, int $depth = 0): mixed
    {
        // Prevent infinite recursion
        if ($depth > 5) {
            return '[max_depth_reached]';
        }

        if (is_array($data)) {
            // Limit array size
            if (count($data) > 50) {
                $data = array_slice($data, 0, 50, true);
                $data['[truncated]'] = 'Array truncated - too many items';
            }

            $sanitized = [];
            foreach ($data as $key => $value) {
                // Skip sensitive keys
                if ($this->isSensitiveKey($key)) {
                    $sanitized[$key] = '[redacted]';
                } else {
                    $sanitized[$key] = $this->sanitizeData($value, $depth + 1);
                }
            }

            return $sanitized;
        }

        if (is_string($data)) {
            // Limit string length
            if (strlen($data) > 1000) {
                return substr($data, 0, 1000) . '[truncated]';
            }

            // Remove sensitive patterns
            $data = preg_replace('/\b\d{4}[-\s]?\d{4}[-\s]?\d{4}[-\s]?\d{4}\b/', '[card_number]', $data);
            $data = preg_replace('/\b\d{3}-?\d{2}-?\d{4}\b/', '[ssn]', $data);

            return $data;
        }

        return $data;
    }

    private function isSensitiveKey(string $key): bool
    {
        $sensitiveKeys = [
            'password', 'passwd', 'pwd', 'secret', 'token', 'key', 'api_key',
            'authorization', 'auth', 'csrf', 'csrf_token', 'access_token',
            'refresh_token', 'private_key', 'credit_card', 'card_number',
            'cvv', 'cvc', 'ssn', 'social_security',
        ];

        $lowerKey = strtolower($key);

        foreach ($sensitiveKeys as $sensitiveKey) {
            if (str_contains($lowerKey, $sensitiveKey)) {
                return true;
            }
        }

        return false;
    }

    private function normalizeRoute(string $uri): string
    {
        // Remove query string
        $route = parse_url($uri, PHP_URL_PATH) ?? $uri;

        // Remove trailing slash
        $route = rtrim($route, '/');

        // Replace dynamic segments with placeholders
        $route = preg_replace('/\/\d+/', '/{id}', $route);
        $route = preg_replace('/\/[a-f0-9-]{36}/', '/{uuid}', $route);
        $route = preg_replace('/\/[a-f0-9]{32}/', '/{hash}', $route);

        return $route ?: '/';
    }

    private function isApiRequest(?string $uri): bool
    {
        if (! $uri) {
            return false;
        }

        return str_starts_with($uri, '/api/') ||
               str_contains($_SERVER['HTTP_ACCEPT'] ?? '', 'application/json');
    }

    private function isAjaxRequest(): bool
    {
        return (($_SERVER['HTTP_X_REQUESTED_WITH'] ?? '') === 'XMLHttpRequest') ||
               str_contains($_SERVER['HTTP_ACCEPT'] ?? '', 'application/json');
    }
}
