<?php

namespace App\Services\Api;

class RequestLogSanitizer
{
    public const PAYLOAD_MAX_BYTES = 32768; // 32 KB

    /** Header names that must never be stored (secrets). */
    protected static array $excludedHeaders = [
        'authorization',
        'cookie',
        'set-cookie',
        'x-csrf-token',
        'x-xsrf-token',
    ];

    /** Keys in request body (including nested) to redact. */
    protected static array $redactKeys = [
        'token',
        'key',
        'password',
        'secret',
        'authorization',
    ];

    /** Nested paths to always redact (e.g. environment.token, site.token). */
    protected static array $redactPaths = [
        'environment.token',
        'environment.key',
        'site.token',
        'site.key',
    ];

    /**
     * Sanitize headers for logging: remove sensitive ones.
     */
    public static function sanitizeHeaders(array $headers): array
    {
        $out = [];
        foreach ($headers as $name => $values) {
            $lower = strtolower($name);
            if (in_array($lower, self::$excludedHeaders, true)) {
                continue;
            }
            $out[$name] = is_array($values) ? $values : [$values];
        }
        return $out;
    }

    /**
     * Sanitize and truncate payload (body) for logging.
     */
    public static function sanitizePayload(?array $data): ?array
    {
        if ($data === null) {
            return null;
        }
        $sanitized = self::redactArray($data);
        $json = json_encode($sanitized, JSON_UNESCAPED_UNICODE);
        if ($json === false) {
            return ['_error' => 'Could not encode payload'];
        }
        if (strlen($json) <= self::PAYLOAD_MAX_BYTES) {
            return $sanitized;
        }
        return ['_truncated' => true, '_bytes_original' => strlen($json), '_preview' => mb_strcut($json, 0, self::PAYLOAD_MAX_BYTES - 50, 'UTF-8') . '...[truncated]'];
    }

    /**
     * Redact sensitive keys in a subtree (e.g. environment or site from body). Public for use in middleware.
     */
    public static function redactSubtree(array $data): array
    {
        return self::redactArray($data, '');
    }

    /**
     * Redact sensitive keys in array (recursive). Also redact known paths like environment.token.
     */
    protected static function redactArray(array $data, string $path = ''): array
    {
        $out = [];
        foreach ($data as $key => $value) {
            $keyLower = is_string($key) ? strtolower($key) : $key;
            $currentPath = $path ? $path . '.' . $key : (string) $key;

            if (is_array($value)) {
                $out[$key] = self::redactArray($value, $currentPath);
                continue;
            }

            $shouldRedact = false;
            if (is_string($key) && in_array($keyLower, self::$redactKeys, true)) {
                $shouldRedact = true;
            }
            foreach (self::$redactPaths as $redactPath) {
                if (str_starts_with($currentPath . '.', $redactPath . '.') || $currentPath === $redactPath) {
                    $shouldRedact = true;
                    break;
                }
            }
            $out[$key] = $shouldRedact ? '[REDACTED]' : $value;
        }
        return $out;
    }

    /**
     * Payload for DB storage (alias; same as sanitizePayload).
     */
    public static function payloadForStorage(?array $data): ?array
    {
        return self::sanitizePayload($data);
    }
}
