<?php

declare(strict_types=1);

namespace App\Logging;

use Monolog\LogRecord;
use Monolog\Processor\ProcessorInterface;

/**
 * Monolog-процессор: маскирует ПДн в логах перед записью.
 *
 * Закрывает Medium go-live: laravel.log (LOG_LEVEL=debug) мог сохранить телефон/email
 * открытым, если они попадут в текст исключения или контекст. Процессор ловит ВСЕ
 * записи каналов, к которым подключён (см. App\Logging\ScrubPii + config/logging.php),
 * централизованно — надёжнее правки отдельных вызовов Log::.
 */
final class PiiScrubbingProcessor implements ProcessorInterface
{
    /**
     * Телефоны РФ: 11 цифр в формате 7XXXXXXXXXX / 8XXXXXXXXXX / +7XXXXXXXXXX.
     * Lookbehind/lookahead не дают маскировать часть более длинной цифровой строки
     * (например 14-значный технический id).
     */
    private const PHONE_PATTERN = '/(?<!\d)(?:\+?7|8)\d{10}(?!\d)/';

    private const EMAIL_PATTERN = '/[\p{L}0-9._%+\-]+@[\p{L}0-9.\-]+\.\p{L}{2,}/u';

    public function __invoke(LogRecord $record): LogRecord
    {
        return $record->with(
            message: $this->scrub($record->message),
            context: $this->scrubArray($record->context),
            extra: $this->scrubArray($record->extra),
        );
    }

    private function scrub(string $value): string
    {
        $value = preg_replace(self::PHONE_PATTERN, '[PHONE]', $value) ?? $value;

        return preg_replace(self::EMAIL_PATTERN, '[EMAIL]', $value) ?? $value;
    }

    /**
     * @param  array<array-key, mixed>  $data
     * @return array<array-key, mixed>
     */
    private function scrubArray(array $data): array
    {
        $result = [];
        foreach ($data as $key => $value) {
            if (is_string($value)) {
                $result[$key] = $this->scrub($value);
            } elseif (is_array($value)) {
                $result[$key] = $this->scrubArray($value);
            } else {
                $result[$key] = $value;
            }
        }

        return $result;
    }
}
