<?php

declare(strict_types=1);

/** APO-196 Card 3 — POST /api/v1/administration/updates/apply (exposeToMcp:true, APO-205-007). */
final class MyronApplyApplicationUpdateAction implements MyronAction
{
    public function __construct(private readonly MyronSiteSetupActionService $service) {}
    public function metadata(): MyronActionMetadata
    {
        return new MyronActionMetadata(
            uri: '/api/v1/administration/updates/apply', method: 'POST',
            domain: 'administration', resource: 'application-update', verb: 'apply',
            inputSchema: [
                'type' => 'object',
                'properties' => [
                    'filename'      => ['type' => 'string'],
                    'contentType'   => ['type' => 'string'],
                    'contentBase64' => ['type' => 'string'],
                    'sizeBytes'     => ['type' => 'integer'],
                    'sha256'        => ['type' => 'string'],
                ],
                'required' => ['filename', 'contentType', 'contentBase64', 'sizeBytes', 'sha256'],
            ],
            outputSchema: ['type' => 'object', 'properties' => []],
            requiredScope: MyronApiScopes::ADMINISTRATION_DESTRUCTIVE,
            summary: 'Apply an uploaded application update package (JSON-content envelope; ZIP). Mutates install files; the current PHP process may restart mid-apply.',
            sideEffects: ['applies file operations from the update package'],
            preconditions: ['caller is administrator', 'envelope decodes cleanly'],
            postconditions: ['package applied'],
            audienceClass: 4, adminAliases: ['apply-application-update'],
            snapshotInclude: false, exposeToMcp: true,
        );
    }
    public function execute(array $input, MyronActionCallerContext $caller): MyronActionResult
    {
        $entitlement = $this->requireCapability('updates.security');
        if ($entitlement !== null) {
            return $entitlement;
        }
        try {
            $envelope = [
                'filename'      => $input['filename']      ?? '',
                'contentType'   => $input['contentType']   ?? 'application/zip',
                'contentBase64' => $input['contentBase64'] ?? '',
                'sizeBytes'     => $input['sizeBytes']     ?? 0,
                'sha256'        => $input['sha256']        ?? '',
            ];
            $tempDir = dirname(__DIR__, 3) . '/tmp/uploads';
            $decoder = new MyronJsonContentEnvelope($tempDir, ['application/zip', 'application/x-zip-compressed']);
            $decoded = $decoder->decode($envelope);
            try {
                return MyronActionResult::ok($this->service->applyApplicationUpdate(
                    $decoded->asUploadedFileArray(),
                    $caller
                ));
            } finally {
                $decoded->cleanup();
            }
        } catch (MyronJsonContentEnvelopeError $e) { return MyronActionResult::error('input_invalid', $e->getMessage()); }
        catch (MyronActionPreconditionError $e) { return MyronActionResult::error('precondition_failed', $e->getMessage()); }
        catch (Throwable $e) { return MyronActionResult::error('business_rule_failed', $e->getMessage()); }
    }

    private function requireCapability(string $capability): ?MyronActionResult
    {
        if (!function_exists('myronEntitlementGate')) {
            return null;
        }
        try {
            $decision = myronEntitlementGate()->evaluate($capability);
        } catch (Throwable) {
            return MyronActionResult::error('entitlement_denied', 'This feature is not available for the current license.');
        }
        return in_array((string) ($decision['outcome'] ?? ''), ['allowed', 'warn'], true)
            ? null
            : MyronActionResult::error('entitlement_denied', 'This feature is not available for the current license.');
    }
}
