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

use CpCreation\VitiCore\Commerce\Catalog\Enrollment\CatalogEnrollmentConfigurationException;
use CpCreation\VitiCore\Commerce\Catalog\Enrollment\CatalogEnrollmentPayloadFactory;
use CpCreation\VitiCore\Commerce\Catalog\Enrollment\CatalogEnrollmentPayloadProvider;
use CpCreation\VitiCore\Commerce\Catalog\Enrollment\CatalogEnrollmentSupersededException;
use CpCreation\VitiCore\Commerce\Catalog\Enrollment\CatalogHubRegistrar;
use CpCreation\VitiCore\Commerce\Catalog\Enrollment\CatalogHubRegistrationException;
use CpCreation\VitiCore\Commerce\Catalog\Enrollment\HttpCatalogHubRegistrar;
use CpCreation\VitiCore\Commerce\Catalog\Enrollment\Model\CatalogEnrollmentRequest;
use CpCreation\VitiCore\Commerce\Catalog\Enrollment\Repository\CatalogEnrollmentRequestRepository;
use CpCreation\VitiCore\Commerce\Catalog\Enrollment\Repository\CatalogEnrollmentRequestStore;
use CpCreation\VitiCore\Commerce\Catalog\Model\CatalogConfiguration;
use CpCreation\VitiCore\Commerce\Catalog\Repository\CatalogConfigurationRepository;
use CpCreation\VitiCore\Commerce\Catalog\Repository\CatalogConfigurationStore;
use Doctrine\ORM\EntityManagerInterface;
use Psr\Log\LoggerInterface;
use Symfony\Component\DependencyInjection\Attribute\Autowire;
use Symfony\Component\Messenger\Attribute\AsMessageHandler;
use Symfony\Component\Messenger\Exception\RecoverableMessageHandlingException;
use Symfony\Component\Messenger\Exception\UnrecoverableMessageHandlingException;

#[AsMessageHandler]
final readonly class DeliverCatalogEnrollmentMessageHandler
{
    public function __construct(
        #[Autowire(service: CatalogEnrollmentRequestRepository::class)]
        private CatalogEnrollmentRequestStore $requestStore,
        #[Autowire(service: CatalogEnrollmentPayloadFactory::class)]
        private CatalogEnrollmentPayloadProvider $payloadFactory,
        #[Autowire(service: HttpCatalogHubRegistrar::class)]
        private CatalogHubRegistrar $registrar,
        #[Autowire(service: CatalogConfigurationRepository::class)]
        private CatalogConfigurationStore $configurationStore,
        private EntityManagerInterface $entityManager,
        private ?LoggerInterface $logger = null,
    ) {}

    public function __invoke(DeliverCatalogEnrollmentMessage $message): void
    {
        $request = $this->requestStore->findOneByRequestId($message->requestId);
        if (!$request instanceof CatalogEnrollmentRequest || !$request->isPrepared() || $request->isDeliveryComplete()) {
            return;
        }

        $attemptedAt = new \DateTimeImmutable('now', new \DateTimeZone('UTC'));
        $configuration = null;

        try {
            $configuration = $this->configurationStore->get();
            if ($configuration instanceof CatalogConfiguration && !$configuration->isHubConnectionEnabled()) {
                return;
            }
            if (!$configuration instanceof CatalogConfiguration
                || $configuration->getInstanceId()->toString() !== $request->getInstanceId()->toString()
            ) {
                $configuration = null;
                throw new CatalogEnrollmentConfigurationException('La configuration Commerce active est introuvable ou ne correspond pas à la demande.');
            }

            $payload = $this->payloadFactory->create($request);
            $response = $this->registrar->register($payload);
        } catch (CatalogEnrollmentSupersededException $exception) {
            $request->markPermanentFailure($attemptedAt, $exception->getMessage());
            $this->entityManager->flush();
            $this->logger?->info('Ancienne demande d’enrôlement Commerce neutralisée.', [
                'request_id' => $message->requestId,
            ]);

            return;
        } catch (CatalogEnrollmentConfigurationException $exception) {
            $this->failFromConfiguration($request, $attemptedAt, $exception, $configuration);
        } catch (CatalogHubRegistrationException $exception) {
            if ($exception->retryable) {
                $this->retry($request, $attemptedAt, $exception->getMessage(), $exception, $configuration);
            }

            $configuration?->markError($exception->getMessage());
            $request->markPermanentFailure($attemptedAt, $exception->getMessage(), $exception->response);
            $this->entityManager->flush();
            $this->logger?->error($exception->getMessage(), [
                'request_id' => $message->requestId,
                'status_code' => $exception->statusCode,
            ]);

            throw new UnrecoverableMessageHandlingException($exception->getMessage(), previous: $exception);
        }

        $configuration->applyHubVerificationMetadata(
            $response->jwtPublicKeyPem,
            $response->jwtIssuer,
            $response->jwtKeyId,
        );
        $configuration->markEnrolled($response->sourceId, $response->webhookUrl, $attemptedAt);

        $request->markRegistered(
            $attemptedAt,
            $response->sourceId,
            $response->webhookUrl,
            $response->status,
            $response->raw,
        );
        $this->entityManager->flush();
        $this->logger?->info('Catalogue enrôlé dans le hub Commerce.', [
            'request_id' => $message->requestId,
            'instance_id' => $response->instanceId,
            'source_id' => $response->sourceId,
            'status' => $response->status,
        ]);
    }

    private function failFromConfiguration(
        CatalogEnrollmentRequest $request,
        \DateTimeImmutable $attemptedAt,
        CatalogEnrollmentConfigurationException $exception,
        ?CatalogConfiguration $configuration,
    ): never {
        if ($exception->retryable) {
            $this->retry($request, $attemptedAt, $exception->getMessage(), $exception, $configuration);
        }

        $configuration?->markError($exception->getMessage());
        $request->markPermanentFailure($attemptedAt, $exception->getMessage());
        $this->entityManager->flush();

        throw new UnrecoverableMessageHandlingException($exception->getMessage(), previous: $exception);
    }

    private function retry(
        CatalogEnrollmentRequest $request,
        \DateTimeImmutable $attemptedAt,
        string $error,
        ?\Throwable $previous = null,
        ?CatalogConfiguration $configuration = null,
    ): never {
        $configuration?->markError($error);
        $request->markRetryableFailure($attemptedAt, $error);
        $this->entityManager->flush();
        $this->logger?->warning($error, [
            'request_id' => $request->getRequestId()->toString(),
            'attempt' => $request->getDeliveryAttempts(),
        ]);

        throw new RecoverableMessageHandlingException(
            $error,
            previous: $previous,
            retryDelay: 300_000,
        );
    }
}
