<?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\Bridge\Symfony\Bundle\Controller\Admin;

use CpCreation\VitiCore\Bridge\Symfony\Bundle\Form\Admin\CatalogConfigurationType;
use CpCreation\VitiCore\Commerce\Catalog\Authorization\CatalogHubAuthorizationException;
use CpCreation\VitiCore\Commerce\Catalog\Authorization\CatalogHubAuthorizationFlow;
use CpCreation\VitiCore\Commerce\Catalog\Configuration\CatalogSecretCipher;
use CpCreation\VitiCore\Commerce\Catalog\Model\CatalogConfiguration;
use CpCreation\VitiCore\Commerce\Catalog\Model\CatalogConfigurationStatus;
use CpCreation\VitiCore\Commerce\Catalog\Repository\CatalogConfigurationRepository;
use CpCreation\VitiCore\Contact\ContactProvider;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\Form\FormError;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Attribute\Route;
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
use Symfony\Component\Security\Http\Attribute\IsGranted;
use Symfony\Contracts\Translation\TranslatorInterface;

#[Route('/configuration/commerce')]
#[IsGranted('ROLE_SUPER_ADMIN')]
final class CatalogConfigurationController extends AbstractController
{
    public function __construct(
        private readonly CatalogConfigurationRepository $configurationRepository,
        private readonly CatalogSecretCipher $secretCipher,
        private readonly CatalogHubAuthorizationFlow $authorizationFlow,
        private readonly ContactProvider $contactProvider,
        private readonly TranslatorInterface $translator,
    ) {}

    #[Route('', name: 'admin_commerce_configuration', methods: ['GET', 'POST'])]
    public function index(Request $request): Response
    {
        $configuration = $this->configurationRepository->getOrCreate();
        $wasConnectionEnabled = $configuration->isHubConnectionEnabled();
        $previousHubUrl = $configuration->getHubUrl();
        $form = $this->createForm(CatalogConfigurationType::class, $configuration);
        $form->handleRequest($request);

        if ($form->isSubmitted() && $form->isValid()) {
            $forceReconnect = '1' === $request->request->get('reconnect');
            if (!$configuration->isHubConnectionEnabled()) {
                $configuration->disableHubConnection();
                $this->configurationRepository->save($configuration);
                $this->addFlash('alert-success', $this->translator->trans('commerce_catalog.admin.saved'));

                return $this->redirectToRoute('admin_commerce_configuration');
            }

            $mustResetConnection = $previousHubUrl !== $configuration->getHubUrl()
                || CatalogConfigurationStatus::ERROR === $configuration->getStatus()
                || !$this->hasUsableConnection($configuration)
                || $forceReconnect;
            if ($mustResetConnection) {
                $configuration->resetConnection();
            }
            if (!$wasConnectionEnabled
                || $mustResetConnection
            ) {
                try {
                    $configuration->prepareHubConnection($this->secretCipher);
                } catch (\LogicException) {
                    $form->get('hubUrl')->addError(new FormError(
                        $this->translator->trans('commerce_catalog.admin.hub_url_required'),
                    ));
                }
            }

            if ($form->isValid() && !$this->hasUsableConnection($configuration)) {
                try {
                    $callbackUrl = $this->generateUrl(
                        'admin_commerce_authorization_callback',
                        referenceType: UrlGeneratorInterface::ABSOLUTE_URL,
                    );
                    $authorizationUrl = $this->authorizationFlow->begin(
                        $configuration,
                        $callbackUrl,
                        $this->publicBaseUrl($request),
                        $this->contactProvider->getBusinessName()
                            ?? $this->translator->trans('commerce_catalog.admin.default_instance_name'),
                    );
                    $this->configurationRepository->save($configuration);

                    return $this->redirect($authorizationUrl);
                } catch (CatalogHubAuthorizationException $exception) {
                    $configuration->markError($exception->getMessage());
                    $form->get('hubUrl')->addError(new FormError(
                        $this->translator->trans('commerce_catalog.admin.authorization_start_failed'),
                    ));
                }
            }

            if ($form->isValid()) {
                $this->configurationRepository->save($configuration);
                $this->addFlash('alert-success', $this->translator->trans('commerce_catalog.admin.saved'));

                return $this->redirectToRoute('admin_commerce_configuration');
            }
        }

        return $this->render('@CpCreationVitiCore/admin/commerce/configuration.html.twig', [
            'form' => $form->createView(),
            'item' => $configuration,
        ]);
    }

    #[Route('/callback', name: 'admin_commerce_authorization_callback', methods: ['GET'])]
    public function authorizationCallback(Request $request): Response
    {
        $configuration = $this->configurationRepository->get();
        if (null === $configuration || !$configuration->isHubConnectionEnabled()) {
            throw $this->createNotFoundException();
        }

        if ('access_denied' === $request->query->get('error')) {
            $state = $request->query->get('state');
            if (!\is_string($state) || !$configuration->matchesAuthorizationState($state)) {
                throw $this->createNotFoundException();
            }
            $configuration->markError('L’autorisation a été refusée dans le hub Commerce.');
            $configuration->clearAuthorization();
            $this->configurationRepository->save($configuration);
            $this->addFlash('alert-danger', $this->translator->trans('commerce_catalog.admin.authorization_denied'));

            return $this->redirectToRoute('admin_commerce_configuration');
        }

        $code = $request->query->get('code');
        $state = $request->query->get('state');
        if (!\is_string($code) || !\is_string($state)) {
            throw $this->createNotFoundException();
        }
        if (!$configuration->matchesAuthorizationState($state)) {
            throw $this->createNotFoundException();
        }

        try {
            $callbackUrl = $this->generateUrl(
                'admin_commerce_authorization_callback',
                referenceType: UrlGeneratorInterface::ABSOLUTE_URL,
            );
            $this->authorizationFlow->complete($configuration, $code, $state, $callbackUrl);
            $this->configurationRepository->save($configuration);
            $this->addFlash('alert-success', $this->translator->trans('commerce_catalog.admin.authorization_succeeded'));
        } catch (CatalogHubAuthorizationException $exception) {
            $configuration->markError($exception->getMessage());
            $this->configurationRepository->save($configuration);
            $this->addFlash('alert-danger', $this->translator->trans('commerce_catalog.admin.authorization_failed'));
        }

        return $this->redirectToRoute('admin_commerce_configuration');
    }

    private function publicBaseUrl(Request $request): string
    {
        $basePath = mb_rtrim($request->getBaseUrl(), '/');
        if (str_ends_with($basePath, '.php')) {
            $basePath = mb_rtrim((string) pathinfo($basePath, \PATHINFO_DIRNAME), '/.');
        }

        return mb_rtrim($request->getSchemeAndHttpHost(), '/')
            . ('' !== $basePath ? '/' . mb_ltrim($basePath, '/') : '');
    }

    private function hasUsableConnection(CatalogConfiguration $configuration): bool
    {
        if (!$configuration->hasUsableEnrollmentToken()
            || null === $configuration->getJwtIssuer()
            || null === $configuration->getJwtKeyId()
            || null === $configuration->getJwtPublicKeyPem()
        ) {
            return false;
        }

        try {
            return null !== $configuration->revealEnrollmentToken($this->secretCipher)
                && null !== $configuration->revealWebhookSecret($this->secretCipher);
        } catch (\RuntimeException) {
            // Une rotation d'APP_SECRET rend les anciennes enveloppes illisibles :
            // une nouvelle autorisation du hub est alors obligatoire.
            return false;
        }
    }
}
