<?php

declare(strict_types=1);

namespace App\Shared\Infrastructure\Monitoring;

use const FILTER_VALIDATE_EMAIL;

use function is_array;
use function is_string;

use Sentry\Event;
use Sentry\EventHint;

/**
 * Scrubs PII from Sentry events before sending to GlitchTip.
 *
 * Critical for RGPD compliance (NFR-S3): No personal data in error reports.
 * This callback runs as the last step before sending to the error tracking service.
 *
 * @see Story 1.8 - T1.4: Filter PII before send (scrubber)
 */
final class SentryBeforeSendCallback
{
    public function __invoke(Event $event, ?EventHint $hint): Event
    {
        // Scrub request data
        $request = $event->getRequest();
        if (!empty($request)) {
            $this->scrubArray($request);
            $event->setRequest($request);
        }

        // Scrub extra context
        $extra = $event->getExtra();
        if (!empty($extra)) {
            $this->scrubArray($extra);
            $event->setExtra($extra);
        }

        // Scrub tags that might contain PII
        $tags = $event->getTags();
        if (!empty($tags)) {
            $this->scrubStringArray($tags);
            $event->setTags($tags);
        }

        // Never drop the event - we want all errors tracked
        return $event;
    }

    /**
     * Recursively scrub PII from an array.
     *
     * @param array<array-key, mixed> $data
     */
    private function scrubArray(array &$data): void
    {
        foreach ($data as $key => &$value) {
            if (is_string($key) && $this->isPiiKey($key)) {
                $value = '[FILTERED]';
            } elseif (is_array($value)) {
                $this->scrubArray($value);
            } elseif (is_string($value) && $this->looksLikePii($value)) {
                $value = '[FILTERED]';
            }
        }
    }

    /**
     * Scrub PII from a string-only array (tags).
     *
     * @param array<string, string> $data
     */
    private function scrubStringArray(array &$data): void
    {
        foreach ($data as $key => &$value) {
            if ($this->isPiiKey($key) || $this->looksLikePii($value)) {
                $value = '[FILTERED]';
            }
        }
    }

    private function isPiiKey(string $key): bool
    {
        return PiiPatterns::isSensitiveKey($key);
    }

    private function looksLikePii(string $value): bool
    {
        // Filter email-like patterns
        if (filter_var($value, FILTER_VALIDATE_EMAIL) !== false) {
            return true;
        }

        // Filter JWT tokens
        if (preg_match('/^eyJ[a-zA-Z0-9_-]+\.eyJ[a-zA-Z0-9_-]+\.[a-zA-Z0-9_-]+$/', $value)) {
            return true;
        }

        // Filter UUIDs in specific contexts (but not all - some are legitimate IDs)
        // We keep UUIDs as they're often needed for debugging

        return false;
    }
}
