<?php

declare(strict_types=1);

namespace ShieldX\Security;

defined('ABSPATH') || exit;

final class VirtualPatchUpdateService
{
    /** @var VirtualPatchBundleVerifier */
    private $verifier;

    /** @var VirtualPatchRepository */
    private $repository;

    /** @var VirtualPatchCatalogVerifier */
    private $catalogVerifier;

    public function __construct(
        ?VirtualPatchBundleVerifier $verifier = null,
        ?VirtualPatchRepository $repository = null,
        ?VirtualPatchCatalogVerifier $catalogVerifier = null
    )
    {
        $this->verifier = $verifier ?: new VirtualPatchBundleVerifier();
        $this->repository = $repository ?: new VirtualPatchRepository();
        $this->catalogVerifier = $catalogVerifier ?: new VirtualPatchCatalogVerifier();
    }

    /**
     * This is the only core entry point for definition transports. Core never
     * downloads a bundle and has no knowledge of a feed URL or entitlement.
     *
     * @return array<string,mixed>
     */
    public function processSignedBundle(string $payload, string $source = 'addon'): array
    {
        $state = $this->repository->feedState();
        $verified = $this->verifier->verifyJson($payload, (int) ($state['sequence'] ?? 0));
        if (!$verified['ok']) {
            $this->repository->recordFailure($verified);
            $this->audit('warning', 'virtual_patch.update_failed', [
                'source' => $this->safeSource($source),
                'error_code' => $verified['error_code'],
            ]);

            return $verified;
        }

        $summary = $this->repository->applyBundle($verified['bundle']);
        $this->audit('info', 'virtual_patch.update_completed', [
            'source' => $this->safeSource($source),
            'sequence' => (int) ($verified['bundle']['sequence'] ?? 0),
            'version' => (string) ($verified['bundle']['version'] ?? ''),
            'summary' => $summary,
        ]);

        if (function_exists('do_action')) {
            do_action('shieldx_virtual_patch_bundle_accepted', $verified['bundle'], $summary);
        }

        return [
            'ok' => true,
            'error_code' => '',
            'message' => 'Threat definitions were verified and activated in monitor mode.',
            'bundle' => $verified['bundle'],
            'summary' => $summary,
        ];
    }

    /** @return array<string,mixed> */
    public function processSignedCatalog(string $payload, string $source = 'addon'): array
    {
        $state = $this->repository->catalogState();
        $payloadHash = hash('sha256', $payload);
        $storedExpiry = strtotime((string) ($state['expires_at'] ?? ''));
        if ($storedExpiry !== false && $storedExpiry > time()
            && (string) ($state['sha256'] ?? '') !== ''
            && hash_equals((string) $state['sha256'], $payloadHash)) {
            return [
                'ok' => true,
                'error_code' => '',
                'message' => 'The installed threat repository catalog is current.',
                'catalog' => [],
            ];
        }
        $verified = $this->catalogVerifier->verifyJson($payload, (int) ($state['sequence'] ?? 0));
        if (!$verified['ok']) {
            $this->repository->recordCatalogFailure($verified);
            $this->audit('warning', 'virtual_patch.catalog_failed', [
                'source' => $this->safeSource($source),
                'error_code' => $verified['error_code'],
            ]);

            return $verified;
        }

        $this->repository->applyCatalog($verified['catalog'], $payloadHash);
        $this->audit('info', 'virtual_patch.catalog_completed', [
            'source' => $this->safeSource($source),
            'sequence' => (int) ($verified['catalog']['sequence'] ?? 0),
            'version' => (string) ($verified['catalog']['version'] ?? ''),
            'repositories' => count((array) ($verified['catalog']['repositories'] ?? [])),
        ]);

        return [
            'ok' => true,
            'error_code' => '',
            'message' => 'The threat repository catalog was verified.',
            'catalog' => $verified['catalog'],
        ];
    }

    /**
     * @param array<string,mixed> $catalogEntry
     * @return array<string,mixed>
     */
    public function processSignedRepositoryBundle(string $payload, array $catalogEntry, string $source = 'addon'): array
    {
        $repositoryId = (string) ($catalogEntry['repository_id'] ?? '');
        $expectedBytes = (int) ($catalogEntry['bytes'] ?? 0);
        $expectedHash = (string) ($catalogEntry['sha256'] ?? '');
        if ($repositoryId === '' || $expectedBytes !== strlen($payload)
            || preg_match('/^[a-f0-9]{64}$/', $expectedHash) !== 1
            || !hash_equals($expectedHash, hash('sha256', $payload))) {
            $failed = [
                'ok' => false,
                'error_code' => 'artifact_binding',
                'message' => 'The repository artifact does not match the signed catalog.',
                'bundle' => [],
            ];
            if ($repositoryId !== '') {
                $this->repository->recordFailure($failed, $repositoryId);
            }

            return $failed;
        }

        $state = $this->repository->feedState($repositoryId);
        $verified = $this->verifier->verifyJson($payload, (int) ($state['sequence'] ?? 0), $catalogEntry);
        if (!$verified['ok']) {
            $this->repository->recordFailure($verified, $repositoryId);
            $this->audit('warning', 'virtual_patch.repository_failed', [
                'source' => $this->safeSource($source),
                'repository_id' => $repositoryId,
                'error_code' => $verified['error_code'],
            ]);

            return $verified;
        }

        $summary = $this->repository->applyRepositoryBundle($verified['bundle'], $catalogEntry);
        $this->audit('info', 'virtual_patch.repository_completed', [
            'source' => $this->safeSource($source),
            'repository_id' => $repositoryId,
            'sequence' => (int) ($verified['bundle']['sequence'] ?? 0),
            'summary' => $summary,
        ]);
        if (function_exists('do_action')) {
            do_action('shieldx_virtual_patch_repository_accepted', $verified['bundle'], $summary, $catalogEntry);
        }

        return [
            'ok' => true,
            'error_code' => '',
            'message' => 'The component threat definitions were verified and activated in monitor mode.',
            'bundle' => $verified['bundle'],
            'summary' => $summary,
        ];
    }

    /** @param array<string,mixed> $context */
    private function audit(string $severity, string $event, array $context): void
    {
        if (function_exists('do_action')) {
            do_action('shieldx_audit_event', $severity, $event, $context, ['time' => gmdate('c')]);
        }
    }

    private function safeSource(string $source): string
    {
        $source = preg_replace('/[^a-z0-9._-]/i', '', $source);

        return substr(is_string($source) ? $source : 'addon', 0, 80);
    }
}
