<?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\Enrollment;

use CpCreation\VitiCore\Commerce\Catalog\CatalogContract;
use CpCreation\VitiCore\Commerce\Catalog\CatalogInstanceProjector;
use CpCreation\VitiCore\Commerce\Catalog\Configuration\CatalogHubConfiguration;
use CpCreation\VitiCore\Commerce\Catalog\Configuration\CatalogIntegrationConfiguration;
use CpCreation\VitiCore\Commerce\Catalog\Configuration\CatalogWebhookEndpointResolver;
use CpCreation\VitiCore\Commerce\Catalog\Enrollment\Model\CatalogEnrollmentRequest;
use Symfony\Component\Routing\RequestContext;

final readonly class CatalogEnrollmentPayloadFactory implements CatalogEnrollmentPayloadProvider
{
    public function __construct(
        private CatalogIntegrationConfiguration $catalogConfiguration,
        private CatalogHubConfiguration $hubConfiguration,
        private CatalogWebhookEndpointResolver $webhookEndpointResolver,
        private CatalogInstanceProjector $instanceProjector,
        private RequestContext $requestContext,
    ) {}

    public function currentContext(): ?CatalogEnrollmentContext
    {
        if (
            !$this->catalogConfiguration->isHubConnectionEnabled()
            || !$this->hubConfiguration->isEnrollmentReady()
            || !$this->webhookEndpointResolver->isHubConnectionReady()
        ) {
            return null;
        }

        try {
            $instanceId = $this->catalogConfiguration->instanceId();
            $baseUrl = $this->baseUrl();
            $webhookSecret = $this->webhookEndpointResolver->secret();
            $enrollmentToken = $this->hubConfiguration->enrollmentToken();
            $authAudience = $this->hubConfiguration->jwtAudience();
            $hubUrl = $this->hubConfiguration->hubUrl();
            $instance = $this->instanceProjector->project();
        } catch (\LogicException) {
            return null;
        }

        if (
            mb_strlen($webhookSecret) < 32
            || mb_strlen($webhookSecret) > 255
            || '' === mb_trim($authAudience)
            || mb_strlen($authAudience) > 255
            || mb_strlen($baseUrl) > 2048
        ) {
            return null;
        }

        $displayName = mb_trim($instance->seller->display_name);
        $legalName = mb_trim((string) $instance->seller->legal_name);
        $fallbackName = 'Serenity ' . $instanceId;
        $name = mb_substr('' !== $displayName ? $displayName : $fallbackName, 0, 160);
        $merchantName = mb_substr('' !== $legalName ? $legalName : $name, 0, 180);
        $payload = new CatalogEnrollmentPayload(
            schemaVersion: CatalogContract::SCHEMA_VERSION,
            instanceId: $instanceId,
            name: $name,
            merchantName: $merchantName,
            baseUrl: $baseUrl,
            authAudience: $authAudience,
            webhookSecret: $webhookSecret,
            catalogEnabled: $this->catalogConfiguration->isEnabled(),
            monitoringEnabled: $this->catalogConfiguration->isMonitoringEnabled(),
        );

        $fingerprintData = [
            ...$payload->toArray(),
            'hub_url' => mb_rtrim($hubUrl, '/'),
            'enrollment_token_sha256' => hash('sha256', $enrollmentToken),
            'webhook_secret_sha256' => hash('sha256', $webhookSecret),
        ];
        unset($fingerprintData['webhook_secret']);

        return new CatalogEnrollmentContext(
            instanceId: $instanceId,
            configurationFingerprint: hash('sha256', json_encode($fingerprintData, \JSON_THROW_ON_ERROR | \JSON_UNESCAPED_SLASHES)),
            payload: $payload,
        );
    }

    public function create(CatalogEnrollmentRequest $request): CatalogEnrollmentPayload
    {
        $context = $this->currentContext();
        if (!$context instanceof CatalogEnrollmentContext) {
            throw new CatalogEnrollmentConfigurationException('La configuration d’enrôlement du catalogue est incomplète.');
        }
        if (!hash_equals($request->getConfigurationFingerprint(), $context->configurationFingerprint)) {
            throw new CatalogEnrollmentSupersededException('La demande d’enrôlement a été remplacée par une configuration plus récente.');
        }

        return $context->payload;
    }

    private function baseUrl(): string
    {
        $scheme = mb_strtolower(mb_trim($this->requestContext->getScheme()));
        $host = mb_trim($this->requestContext->getHost());
        if (!\in_array($scheme, ['http', 'https'], true) || '' === $host) {
            throw new \LogicException('DEFAULT_URI must contain an absolute HTTP(S) URL for Commerce enrollment.');
        }

        $port = 'https' === $scheme
            ? $this->requestContext->getHttpsPort()
            : $this->requestContext->getHttpPort();
        $defaultPort = 'https' === $scheme ? 443 : 80;
        $authority = $host . ($port !== $defaultPort ? ':' . $port : '');
        $basePath = mb_rtrim($this->requestContext->getBaseUrl(), '/');
        if (str_ends_with($basePath, '.php')) {
            $basePath = mb_rtrim((string) pathinfo($basePath, \PATHINFO_DIRNAME), '/.');
        }

        return $scheme . '://' . $authority . ('' !== $basePath ? '/' . mb_ltrim($basePath, '/') : '');
    }
}
