<?php

declare(strict_types=1);

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

use CpCreation\VitiCore\Cart\CartPricing;
use CpCreation\VitiCore\Cart\Model\Cart;
use CpCreation\VitiCore\Cart\Model\CartItem;
use CpCreation\VitiCore\Shop\Repository\ShopConfigRepository;
use Psr\Log\LoggerInterface;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Messenger\MessageBusInterface;

final readonly class MatomoEcommerceTracker
{
    public function __construct(
        private ShopConfigRepository $shopConfigRepository,
        private MessageBusInterface $messageBus,
        private string $matomoAuthToken = '',
        private ?LoggerInterface $logger = null,
    ) {
    }

    public function trackAddToCart(CartItem $item, ?Request $request = null): void
    {
        $variant = $item->getVariant();
        $variantId = $variant?->getId()?->toString();

        if (null === $variant || null === $variantId) {
            return;
        }

        $cart = $item->getCart();
        $payload = [
            'e_c' => 'Ecommerce',
            'e_a' => 'AddToCart',
            'e_n' => $variant->getFullName(),
            'e_v' => round($item->getTotalFinalTTC() / 100, 2),
            'ec_items' => $this->encodeItems([[
                $variantId,
                $variant->getFullName(),
                $variant->getType()?->getName() ?? '',
                round($item->getFinalPriceTTC() / 100, 2),
                $item->getQuantity(),
            ]]),
        ];

        if ($cart instanceof Cart) {
            $this->enrichPayloadWithAttribution($payload, $cart);
        }

        $this->enrichPayloadWithRequest($payload, $request);
        $this->sendPayload($payload, [
            'event_name' => 'AddToCart',
            'cart_id' => $cart instanceof Cart ? $cart->getId()->toString() : null,
            'cart_item_id' => $item->getId()->toString(),
        ]);
    }

    public function trackInitiateCheckout(Cart $cart, ?Request $request = null): void
    {
        $pricing = $cart->getPricing();
        $payload = [
            'e_c' => 'Ecommerce',
            'e_a' => 'InitiateCheckout',
            'e_n' => $cart->getId()->toString(),
            'e_v' => null !== $pricing ? round($pricing->totalTTC / 100, 2) : null,
        ];

        if (null !== $pricing) {
            $payload['ec_items'] = $this->encodePricingItems($pricing);
        }

        $this->enrichPayloadWithAttribution($payload, $cart);
        $this->enrichPayloadWithCheckoutContext($payload, $cart);
        $this->enrichPayloadWithRequest($payload, $request);
        $this->sendPayload($payload, [
            'event_name' => 'InitiateCheckout',
            'cart_id' => $cart->getId()->toString(),
        ]);
    }

    public function trackAbandonedCart(Cart $cart): void
    {
        $this->trackCartLifecycleEvent($cart, 'AbandonedCart');
    }

    public function trackReactivatedCart(Cart $cart, ?Request $request = null): void
    {
        $this->trackCartLifecycleEvent($cart, 'ReactivatedCart', $request);
    }

    public function trackPaymentFailed(Cart $cart, ?Request $request = null): void
    {
        $this->trackCartLifecycleEvent($cart, 'PaymentFailed', $request);
    }

    public function trackPaymentCanceled(Cart $cart, ?Request $request = null): void
    {
        $this->trackCartLifecycleEvent($cart, 'PaymentCanceled', $request);
    }

    public function trackCartCleared(Cart $cart, ?Request $request = null): void
    {
        $this->trackCartLifecycleEvent($cart, 'CartCleared', $request);
    }

    public function trackOrder(Cart $cart): void
    {
        $config = $this->shopConfigRepository->findOne();

        if (null === $config || !$config->isMatomoConfigured() || !$config->isEnableMatomoEcommerceTracking()) {
            return;
        }

        $pricing = $cart->getPricing();

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

        $items = [];

        foreach ($pricing->lines as $line) {
            if ($line->quantity <= 0) {
                continue;
            }

            $sku = $line->productId ?? $line->variantId ?? $line->itemId;

            if (null === $sku || '' === $sku) {
                continue;
            }

            $items[] = [
                $sku,
                $line->productName ?? $line->variantFullName ?? $line->typeName ?? '',
                $line->typeName ?? '',
                round($line->netTTC / $line->quantity / 100, 2),
                $line->quantity,
            ];
        }

        try {
            $payload = [
                'idgoal' => 0,
                'ec_id' => $cart->getId()->toString(),
                'ec_items' => $this->encodeItems($items),
                'revenue' => round($pricing->totalTTC / 100, 2),
                'ec_st' => round($pricing->totalHT / 100, 2),
                'ec_tx' => round($pricing->totalTax / 100, 2),
                'ec_sh' => round(($cart->getShippingTTC() ?? 0) / 100, 2),
                'ec_dt' => round(($pricing->discountTTC + $pricing->degressiveTTC) / 100, 2),
            ];

            $this->enrichPayloadWithAttribution($payload, $cart);
            $this->enrichPayloadWithCheckoutContext($payload, $cart);

            $this->sendPayload($payload, [
                'event_name' => 'Purchase',
                'cart_id' => $cart->getId()->toString(),
            ]);
        } catch (\JsonException|\Random\RandomException $exception) {
            $this->logger?->warning('Matomo ecommerce tracking failed.', [
                'cart_id' => $cart->getId()->toString(),
                'exception' => $exception,
            ]);
        }
    }

    private function trackCartLifecycleEvent(Cart $cart, string $eventName, ?Request $request = null): void
    {
        $pricing = $cart->getPricing();
        $payload = [
            'e_c' => 'Ecommerce',
            'e_a' => $eventName,
            'e_n' => $cart->getId()->toString(),
            'e_v' => null !== $pricing ? round($pricing->totalTTC / 100, 2) : null,
        ];

        if (null !== $pricing) {
            $payload['ec_items'] = $this->encodePricingItems($pricing);
        }

        $this->enrichPayloadWithAttribution($payload, $cart);
        $this->enrichPayloadWithCheckoutContext($payload, $cart);
        $this->enrichPayloadWithRequest($payload, $request);
        $this->sendPayload($payload, [
            'event_name' => $eventName,
            'cart_id' => $cart->getId()->toString(),
        ]);
    }

    /**
     * @param array<string, mixed> $payload
     * @param array<string, mixed> $context
     */
    private function sendPayload(array $payload, array $context = []): void
    {
        $config = $this->shopConfigRepository->findOne();

        if (null === $config || !$config->isMatomoConfigured() || !$config->isEnableMatomoEcommerceTracking()) {
            return;
        }

        $payload += [
            'idsite' => $config->getMatomoSiteId(),
            'rec' => 1,
            'apiv' => 1,
            'send_image' => 0,
            'rand' => bin2hex(random_bytes(8)),
        ];

        try {
            $this->messageBus->dispatch(new ServerTrackingMessage(
                provider: ServerTrackingMessage::PROVIDER_MATOMO,
                payload: $payload,
                context: $context,
            ));
        } catch (\Throwable $exception) {
            $this->logger?->warning('Matomo ecommerce tracking enqueue failed.', [
                'exception' => $exception,
            ] + $context);
        }
    }

    /**
     * @param array<string, mixed> $payload
     */
    private function enrichPayloadWithRequest(array &$payload, ?Request $request): void
    {
        if (null === $request) {
            return;
        }

        $requestPayload = array_filter([
            'url' => $request->getUri(),
            'urlref' => $request->headers->get('Referer'),
            'ua' => $request->headers->get('User-Agent'),
        ], static fn (mixed $value): bool => null !== $value && '' !== $value);

        if ('' !== mb_trim($this->matomoAuthToken)) {
            $clientIp = $request->getClientIp();

            if (null !== $clientIp && '' !== $clientIp) {
                $requestPayload['cip'] = $clientIp;
            }
        }

        $payload += $requestPayload;
    }

    /**
     * @param array<string, mixed> $payload
     */
    private function enrichPayloadWithAttribution(array &$payload, Cart $cart): void
    {
        $attribution = $cart->getCartCheckout()?->getMarketingAttribution()
            ?? $cart->getMarketingAttribution();

        if (!\is_array($attribution)) {
            return;
        }

        $matomo = \is_array($attribution['matomo'] ?? null) ? $attribution['matomo'] : [];

        if (!isset($payload['cid']) && \is_string($matomo['visitor_id'] ?? null)) {
            $payload['cid'] = $matomo['visitor_id'];
        }

        if (!isset($payload['url']) && \is_string($matomo['url'] ?? null)) {
            $payload['url'] = $matomo['url'];
        }

        if (!isset($payload['urlref']) && \is_string($matomo['referrer'] ?? null)) {
            $payload['urlref'] = $matomo['referrer'];
        }

        if (!isset($payload['ua']) && \is_string($matomo['user_agent'] ?? null)) {
            $payload['ua'] = $matomo['user_agent'];
        }

        if (!isset($payload['cid']) && \is_string($attribution['visitor_id'] ?? null)) {
            $payload['cid'] = $attribution['visitor_id'];
        }

        if (!isset($payload['url']) && \is_string($attribution['landing_url'] ?? null)) {
            $payload['url'] = $attribution['landing_url'];
        }

        if (!isset($payload['urlref']) && \is_string($attribution['referrer'] ?? null)) {
            $payload['urlref'] = $attribution['referrer'];
        }

        $parameters = $attribution['parameters'] ?? null;
        if (!\is_array($parameters)) {
            return;
        }

        $campaignName = $this->firstString($parameters, ['mtm_campaign', 'utm_campaign', 'utm_id']);
        if (null !== $campaignName) {
            $payload['_rcn'] = $campaignName;
        }

        $campaignKeyword = $this->firstString($parameters, ['mtm_keyword', 'utm_term', 'utm_source']);
        if (null !== $campaignKeyword) {
            $payload['_rck'] = $campaignKeyword;
        }
    }

    /**
     * @param array<string, mixed> $payload
     */
    private function enrichPayloadWithCheckoutContext(array &$payload, Cart $cart): void
    {
        $paymentMethod = $cart->getPaymentMethod();
        $customVariables = $this->normalizeCustomVariables([
            ['Checkout status', $cart->getCheckoutStatus()],
            ['Payment type', null !== $paymentMethod ? ($paymentMethod->isCheckoutOnlineMethod() ? 'online' : 'offline') : null],
            ['Payment method', $paymentMethod?->getMethod()],
            ['Document type', $cart->getDocumentType()],
            ['Shipping method', $cart->getCartShipping()?->name ?? $cart->getDeliveryMethod()?->getId()->toString()],
        ]);

        if ([] === $customVariables) {
            return;
        }

        $payload['_cvar'] = json_encode(
            $customVariables,
            \JSON_UNESCAPED_UNICODE | \JSON_UNESCAPED_SLASHES | \JSON_THROW_ON_ERROR,
        );
    }

    /**
     * @param list<array{0: string, 1: string|null}> $variables
     *
     * @return array<string, array{0: string, 1: string}>
     */
    private function normalizeCustomVariables(array $variables): array
    {
        $normalized = [];

        foreach ($variables as $variable) {
            if (null === $variable[1] || '' === $variable[1]) {
                continue;
            }

            $normalized[(string) (\count($normalized) + 1)] = [
                $this->truncateCustomVariableValue($variable[0]),
                $this->truncateCustomVariableValue($variable[1]),
            ];
        }

        return $normalized;
    }

    private function truncateCustomVariableValue(string $value): string
    {
        return mb_substr(mb_trim($value), 0, 200);
    }

    /**
     * @param list<array{0: mixed, 1: mixed, 2: mixed, 3: mixed, 4: mixed}> $items
     */
    private function encodeItems(array $items): string
    {
        return json_encode($items, \JSON_UNESCAPED_UNICODE | \JSON_UNESCAPED_SLASHES | \JSON_THROW_ON_ERROR);
    }

    private function encodePricingItems(CartPricing $pricing): string
    {
        $items = [];

        foreach ($pricing->lines as $line) {
            if ($line->quantity <= 0) {
                continue;
            }

            $sku = $line->productId ?? $line->variantId ?? $line->itemId;

            if (null === $sku || '' === $sku) {
                continue;
            }

            $items[] = [
                $sku,
                $line->productName ?? $line->variantFullName ?? $line->typeName ?? '',
                $line->typeName ?? '',
                round($line->netTTC / $line->quantity / 100, 2),
                $line->quantity,
            ];
        }

        return $this->encodeItems($items);
    }

    /**
     * @param array<mixed> $values
     * @param list<string> $keys
     */
    private function firstString(array $values, array $keys): ?string
    {
        foreach ($keys as $key) {
            $value = $values[$key] ?? null;

            if (!\is_scalar($value)) {
                continue;
            }

            $value = mb_trim((string) $value);

            if ('' !== $value) {
                return $value;
            }
        }

        return null;
    }

}
