<?php

declare(strict_types=1);

/*
 * This file is part of the VITICORE2 package.
 *
 * (c) CP Creation <web@cpcreation.fr>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace CpCreation\VitiCore\Commerce\Catalog\Api\OpenApi;

use ApiPlatform\OpenApi\Factory\OpenApiFactoryInterface;
use ApiPlatform\OpenApi\Model\Components;
use ApiPlatform\OpenApi\Model\Operation;
use ApiPlatform\OpenApi\Model\PathItem;
use ApiPlatform\OpenApi\Model\Paths;
use ApiPlatform\OpenApi\Model\Response;
use ApiPlatform\OpenApi\Model\SecurityScheme;
use ApiPlatform\OpenApi\OpenApi;

final readonly class CommerceOpenApiFactory implements OpenApiFactoryInterface
{
    public const string SECURITY_SCHEME = 'CommerceBearer';

    private const string PATH_PREFIX = '/api/commerce/v1/';

    public function __construct(private OpenApiFactoryInterface $decorated) {}

    /**
     * @param array<string, mixed> $context
     */
    public function __invoke(array $context = []): OpenApi
    {
        $openApi = ($this->decorated)($context);
        $securitySchemes = new \ArrayObject(
            $openApi->getComponents()->getSecuritySchemes()?->getArrayCopy() ?? [],
        );
        $securitySchemes[self::SECURITY_SCHEME] = new SecurityScheme(
            type: 'http',
            description: 'Jeton opaque du service central de synchronisation Commerce.',
            scheme: 'bearer',
            bearerFormat: 'opaque',
        );

        $paths = new Paths();
        foreach ($openApi->getPaths()->getPaths() as $path => $pathItem) {
            $paths->addPath(
                $path,
                str_starts_with($path, self::PATH_PREFIX) ? $this->secure($pathItem) : $pathItem,
            );
        }

        $components = $this->requireCommerceFields($openApi->getComponents())
            ->withSecuritySchemes($securitySchemes);

        return $openApi
            ->withComponents($components)
            ->withPaths($paths);
    }

    private function secure(PathItem $pathItem): PathItem
    {
        if (null !== ($operation = $pathItem->getGet())) {
            $pathItem = $pathItem->withGet($this->secureOperation($operation));
        }
        if (null !== ($operation = $pathItem->getPut())) {
            $pathItem = $pathItem->withPut($this->secureOperation($operation));
        }
        if (null !== ($operation = $pathItem->getPost())) {
            $pathItem = $pathItem->withPost($this->secureOperation($operation));
        }
        if (null !== ($operation = $pathItem->getDelete())) {
            $pathItem = $pathItem->withDelete($this->secureOperation($operation));
        }
        if (null !== ($operation = $pathItem->getOptions())) {
            $pathItem = $pathItem->withOptions($this->secureOperation($operation));
        }
        if (null !== ($operation = $pathItem->getHead())) {
            $pathItem = $pathItem->withHead($this->secureOperation($operation));
        }
        if (null !== ($operation = $pathItem->getPatch())) {
            $pathItem = $pathItem->withPatch($this->secureOperation($operation));
        }
        if (null !== ($operation = $pathItem->getTrace())) {
            $pathItem = $pathItem->withTrace($this->secureOperation($operation));
        }

        return $pathItem;
    }

    private function secureOperation(Operation $operation): Operation
    {
        return $operation
            ->withSecurity([[self::SECURITY_SCHEME => []]])
            ->withResponse(401, new Response(
                description: 'Jeton bearer absent ou invalide.',
                content: new \ArrayObject([
                    'application/problem+json' => [
                        'schema' => [
                            'type' => 'object',
                            'required' => ['type', 'title', 'status', 'detail'],
                            'properties' => [
                                'type' => ['type' => 'string'],
                                'title' => ['type' => 'string'],
                                'status' => ['type' => 'integer', 'enum' => [401]],
                                'detail' => ['type' => 'string'],
                            ],
                        ],
                    ],
                ]),
            ));
    }

    private function requireCommerceFields(Components $components): Components
    {
        $schemas = $components->getSchemas();
        if (null === $schemas) {
            return $components;
        }

        $updatedSchemas = new \ArrayObject($schemas->getArrayCopy());
        foreach ($updatedSchemas as $name => $schema) {
            if (
                !\is_string($name)
                || !str_starts_with($name, 'Catalog')
                || (!\is_array($schema) && !$schema instanceof \ArrayObject)
            ) {
                continue;
            }

            $properties = $schema['properties'] ?? null;
            $propertyNames = $properties instanceof \ArrayObject
                ? array_keys($properties->getArrayCopy())
                : (\is_array($properties) ? array_keys($properties) : []);
            if ([] === $propertyNames) {
                continue;
            }

            $requiredSchema = $schema instanceof \ArrayObject ? clone $schema : $schema;
            $requiredSchema['required'] = array_values($propertyNames);
            $updatedSchemas[$name] = $requiredSchema;
        }

        return $components->withSchemas($updatedSchemas);
    }
}
