<?php

namespace App\Domain\Audit\Services;

/**
 * Masks values that should never sit in a log in the clear (§65).
 *
 * The log records *that* a passport number changed and roughly which one, not
 * the number itself — anyone reading history should not thereby gain access to
 * the identity data.
 */
class ValueMasker
{
    /** Fields never written to the log at all. */
    private const REDACTED = [
        'password', 'password_confirmation', 'remember_token', 'public_token',
        'api_token', 'secret', 'two_factor_secret', 'checksum', 'file_path',
    ];

    /** Fields written partially masked. */
    private const MASKED = [
        'national_id', 'passport_number', 'birth_registration_no',
        'bank_account_number', 'mobile_wallet_number', 'tax_identification_number',
        'driving_license_no', 'document_number', 'certificate_number',
    ];

    public function isRedacted(string $field): bool
    {
        return in_array($field, self::REDACTED, true);
    }

    public function mask(string $field, mixed $value): mixed
    {
        if ($this->isRedacted($field)) {
            return '[redacted]';
        }

        if (! in_array($field, self::MASKED, true) || ! is_string($value) || $value === '') {
            return $value;
        }

        $length = mb_strlen($value);

        if ($length <= 4) {
            return str_repeat('*', $length);
        }

        return mb_substr($value, 0, 2).str_repeat('*', $length - 4).mb_substr($value, -2);
    }

    /**
     * @param  array<string, mixed>  $values
     * @return array<string, mixed>
     */
    public function maskAll(array $values): array
    {
        $masked = [];

        foreach ($values as $field => $value) {
            if ($this->isRedacted($field)) {
                continue; // Not even a placeholder key.
            }

            $masked[$field] = $this->mask($field, $value);
        }

        return $masked;
    }
}
