<?php

declare(strict_types=1);

namespace App\Services\Intune;

final class PolicySnapshotRedactor
{
    public function __construct(
        private readonly ?SecretClassificationService $classificationService = null,
        private readonly ?SecretFingerprintHasher $fingerprintHasher = null,
    ) {}

    /**
     * @param  array<string, mixed>  $payload
     * @return array<string, mixed>
     */
    public function redactPayload(array $payload): array
    {
        $redacted = $this->protectBucket('snapshot', $payload, null);

        return is_array($redacted['value']) ? $redacted['value'] : $payload;
    }

    /**
     * @param  array<int, array<string, mixed>>|null  $assignments
     * @return array<int, array<string, mixed>>|null
     */
    public function redactAssignments(?array $assignments): ?array
    {
        if ($assignments === null) {
            return null;
        }

        $redacted = $this->protectBucket('assignments', $assignments, null);

        return is_array($redacted['value']) ? $redacted['value'] : $assignments;
    }

    /**
     * @param  array<int, array<string, mixed>>|null  $scopeTags
     * @return array<int, array<string, mixed>>|null
     */
    public function redactScopeTags(?array $scopeTags): ?array
    {
        if ($scopeTags === null) {
            return null;
        }

        $redacted = $this->protectBucket('scope_tags', $scopeTags, null);

        return is_array($redacted['value']) ? $redacted['value'] : $scopeTags;
    }

    /**
     * @param  array<string, mixed>  $payload
     * @param  array<int, array<string, mixed>>|null  $assignments
     * @param  array<int, array<string, mixed>>|array<string, mixed>|null  $scopeTags
     */
    public function protect(int $workspaceId, array $payload, ?array $assignments = null, ?array $scopeTags = null): ProtectedSnapshotResult
    {
        $snapshot = $this->protectBucket('snapshot', $payload, $workspaceId);
        $protectedAssignments = $assignments !== null ? $this->protectBucket('assignments', $assignments, $workspaceId) : null;
        $protectedScopeTags = $scopeTags !== null ? $this->protectBucket('scope_tags', $scopeTags, $workspaceId) : null;

        $secretFingerprints = ProtectedSnapshotResult::emptyFingerprints();
        $secretFingerprints['snapshot'] = $snapshot['fingerprints'];
        $secretFingerprints['assignments'] = $protectedAssignments['fingerprints'] ?? [];
        $secretFingerprints['scope_tags'] = $protectedScopeTags['fingerprints'] ?? [];

        return new ProtectedSnapshotResult(
            snapshot: is_array($snapshot['value']) ? $snapshot['value'] : $payload,
            assignments: $protectedAssignments !== null && is_array($protectedAssignments['value']) ? $protectedAssignments['value'] : $assignments,
            scopeTags: $protectedScopeTags !== null && is_array($protectedScopeTags['value']) ? $protectedScopeTags['value'] : $scopeTags,
            secretFingerprints: $secretFingerprints,
            redactionVersion: SecretClassificationService::REDACTION_VERSION,
            protectedPathsCount: count($secretFingerprints['snapshot']) + count($secretFingerprints['assignments']) + count($secretFingerprints['scope_tags']),
        );
    }

    /**
     * @param  array<int, string>  $segments
     * @return array{value: mixed, fingerprints: array<string, string>}
     */
    private function protectBucket(string $sourceBucket, mixed $value, ?int $workspaceId, array $segments = []): array
    {
        if (! is_array($value)) {
            return [
                'value' => $value,
                'fingerprints' => [],
            ];
        }

        $redacted = [];
        $fingerprints = [];

        foreach ($value as $key => $item) {
            $nextSegments = [...$segments, (string) $key];
            $jsonPointer = $this->jsonPointer($nextSegments);

            if (is_string($key) && $this->classifier()->protectsField($sourceBucket, $key, $jsonPointer)) {
                $redacted[$key] = SecretClassificationService::REDACTED;

                if ($workspaceId !== null) {
                    $fingerprints[$jsonPointer] = $this->hasher()->fingerprint($workspaceId, $sourceBucket, $jsonPointer, $item);
                }

                continue;
            }

            $protected = $this->protectBucket($sourceBucket, $item, $workspaceId, $nextSegments);

            $redacted[$key] = $protected['value'];
            $fingerprints = [...$fingerprints, ...$protected['fingerprints']];
        }

        return [
            'value' => $redacted,
            'fingerprints' => $fingerprints,
        ];
    }

    /**
     * @param  array<int, string>  $segments
     */
    private function jsonPointer(array $segments): string
    {
        if ($segments === []) {
            return '/';
        }

        return '/'.implode('/', array_map(
            static fn (string $segment): string => str_replace(['~', '/'], ['~0', '~1'], $segment),
            $segments,
        ));
    }

    private function classifier(): SecretClassificationService
    {
        return $this->classificationService ?? app(SecretClassificationService::class);
    }

    private function hasher(): SecretFingerprintHasher
    {
        return $this->fingerprintHasher ?? app(SecretFingerprintHasher::class);
    }
}
