<?php

namespace CpCreation\VitiCore\Bridge\Symfony\Bundle\Twig\Components\Tracking;

use CpCreation\VitiCore\Cart\Model\Cart;
use CpCreation\VitiCore\Cart\Repository\CartRepository;
use CpCreation\VitiCore\Shop\Model\ShopConfig;
use CpCreation\VitiCore\Shop\Repository\ShopConfigRepository;
use Nelmio\SecurityBundle\EventListener\ContentSecurityPolicyListener;
use Symfony\Component\HttpFoundation\RequestStack;
use Symfony\UX\TwigComponent\Attribute\AsTwigComponent;

#[AsTwigComponent('tracking:Scripts', template: '@components/tracking/Scripts.html.twig')]
final class Scripts
{
    public string $position = 'head';

    private ?ShopConfig $shopConfig = null;

    public function __construct(
        private readonly ShopConfigRepository $shopConfigRepository,
        private readonly CartRepository $cartRepository,
        private readonly ?ContentSecurityPolicyListener $contentSecurityPolicyListener = null,
        private readonly ?RequestStack $requestStack = null,
    ) {
    }

    public function shouldRender(): bool
    {
        return 'body' === $this->position
            ? $this->hasBodyTrackers()
            : $this->hasHeadTrackers();
    }

    public function getGoogleTagManagerId(): ?string
    {
        return $this->getShopConfig()?->getGoogleTagManagerId();
    }

    /**
     * @return list<string>
     */
    public function getGoogleAnalyticsIds(): array
    {
        return $this->getShopConfig()?->getGoogleAnalyticsIds() ?? [];
    }

    /**
     * @return list<string>
     */
    public function getFacebookPixelIds(): array
    {
        return $this->getShopConfig()?->getFacebookPixelIds() ?? [];
    }

    public function getAdditionalMarkup(): ?string
    {
        $config = $this->getShopConfig();

        if (null === $config) {
            return null;
        }

        $markup = 'body' === $this->position
            ? $config->getTrackingBodyScripts()
            : $config->getTrackingHeadScripts();

        if (null === $markup || '' === trim($markup)) {
            return null;
        }

        return $this->injectNonceIntoScriptTags($markup);
    }

    public function getFixedEventMarkup(): ?string
    {
        if ('body' !== $this->position) {
            return null;
        }

        $config = $this->getShopConfig();

        if (null === $config) {
            return null;
        }

        $request = $this->requestStack?->getMainRequest();
        $route = $request?->attributes->get('_route');
        $isContactSuccess = $request?->query->getBoolean('contactSuccess', false) ?? false;
        $warnings = $request?->hasSession() ? $request->getSession()->getFlashBag()->peek('warning') : [];
        $errors = $request?->hasSession() ? $request->getSession()->getFlashBag()->peek('error') : [];

        if (!\is_string($route) || '' === $route) {
            return null;
        }

        if (\in_array($route, ['shop_checkout', 'shop_checkout_cart'], true)) {
            if ([] !== $warnings && $config->isEnablePaymentCanceledEventTracking()) {
                return $this->buildEventMarkup('payment_canceled');
            }

            if ([] !== $errors && $config->isEnablePaymentErrorEventTracking()) {
                return $this->buildEventMarkup('payment_failed');
            }

            if ($config->isEnableCheckoutEventTracking()) {
                return $this->buildEventMarkup('begin_checkout');
            }

            return null;
        }

        if ('shop_checkout_success' === $route && $config->isEnablePaymentSuccessEventTracking()) {
            $cartId = $request?->attributes->get('cartId');
            if (\is_string($cartId) && '' !== $cartId) {
                $cart = $this->cartRepository->find($cartId);
                if ($cart instanceof Cart) {
                    return $this->buildPurchaseEventMarkup($cart);
                }
            }

            return null;
        }

        if ('contact' === $route && $isContactSuccess && $config->isEnableContactSuccessEventTracking()) {
            return $this->buildEventMarkup('generate_lead');
        }

        if ('contact' === $route && $config->isEnableContactPageEventTracking()) {
            return $this->buildEventMarkup('view_contact_page');
        }

        return null;
    }

    private function hasHeadTrackers(): bool
    {
        return null !== $this->getGoogleTagManagerId()
            || [] !== $this->getGoogleAnalyticsIds()
            || [] !== $this->getFacebookPixelIds()
            || null !== $this->getAdditionalMarkup();
    }

    private function hasBodyTrackers(): bool
    {
        return null !== $this->getGoogleTagManagerId()
            || [] !== $this->getFacebookPixelIds()
            || null !== $this->getAdditionalMarkup()
            || null !== $this->getFixedEventMarkup();
    }

    private function getShopConfig(): ?ShopConfig
    {
        if (null !== $this->shopConfig) {
            return $this->shopConfig;
        }

        return $this->shopConfig = $this->shopConfigRepository->findOne();
    }

    private function injectNonceIntoScriptTags(string $markup): string
    {
        $nonce = $this->contentSecurityPolicyListener?->getNonce('script');

        if (null === $nonce || '' === $nonce) {
            return $markup;
        }

        return (string) preg_replace_callback(
            '/<script\b(?![^>]*\bnonce=)([^>]*)>/i',
            static function (array $matches) use ($nonce): string {
                $attributes = $matches[1] ?? '';

                return sprintf('<script nonce="%s"%s>', htmlspecialchars($nonce, \ENT_QUOTES), $attributes);
            },
            $markup,
        );
    }

    /**
     * @param array<string, mixed> $params
     */
    private function buildEventMarkup(string $eventName, array $params = []): string
    {
        $dataLayerPayload = ['event' => $eventName] + $params;
        $dataLayerPayloadJson = $this->encodeJson($dataLayerPayload);
        $paramsJson = $this->encodeJson($params);
        $eventNameJson = $this->encodeJson($eventName);

        $markup = <<<HTML
<script>
window.dataLayer = window.dataLayer || [];
window.dataLayer.push($dataLayerPayloadJson);
if (typeof window.gtag === 'function') {
    gtag('event', $eventNameJson, $paramsJson);
}
</script>
HTML;

        return $this->injectNonceIntoScriptTags($markup);
    }

    private function buildPurchaseEventMarkup(Cart $cart): string
    {
        $pricing = $cart->getPricing();
        if (null === $pricing) {
            return $this->buildEventMarkup('purchase');
        }

        $payload = [
            'transaction_id' => $cart->getToken(),
            'currency' => 'EUR',
            'value' => round($pricing->totalTTC / 100, 2),
            'shipping' => round(($cart->getShippingTTC() ?? 0) / 100, 2),
            'items' => array_map(
                static fn ($line): array => [
                    'item_id' => $line->productId ?? $line->itemId,
                    'item_name' => $line->productName ?? $line->variantFullName ?? $line->typeName ?? 'Produit',
                    'item_variant' => $line->variantFullName,
                    'price' => round($line->netTTC / 100, 2),
                    'quantity' => $line->quantity,
                ],
                $pricing->lines,
            ),
        ];

        $dataLayerPayload = [
            'event' => 'purchase',
            'ecommerce' => $payload,
        ];

        $dataLayerPayloadJson = $this->encodeJson($dataLayerPayload);
        $payloadJson = $this->encodeJson($payload);

        $markup = <<<HTML
<script>
window.dataLayer = window.dataLayer || [];
window.dataLayer.push($dataLayerPayloadJson);
if (typeof window.gtag === 'function') {
    gtag('event', 'purchase', $payloadJson);
}
</script>
HTML;

        return $this->injectNonceIntoScriptTags($markup);
    }

    private function encodeJson(mixed $value): string
    {
        return (string) json_encode(
            $value,
            \JSON_UNESCAPED_UNICODE
            | \JSON_UNESCAPED_SLASHES
            | \JSON_HEX_TAG
            | \JSON_HEX_AMP
            | \JSON_HEX_APOS
            | \JSON_HEX_QUOT
            | \JSON_THROW_ON_ERROR
        );
    }
}
