<?php

/*
 * 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\Cart;

use CpCreation\VitiCore\Cart\Model\Cart;
use CpCreation\VitiCore\Shop\Model\Translation\DeliveryMethodTranslation;

final class CustomerOrderInsightsBuilder
{
    /**
     * @param list<Cart> $orders
     *
     * @return array{
     *     summary: array{
     *         order_count: int,
     *         priced_order_count: int,
     *         total_spent_ttc: int,
     *         average_basket_ttc: ?int,
     *         total_quantity: int,
     *         first_order_at: ?\DateTimeInterface,
     *         last_order_at: ?\DateTimeInterface
     *     },
     *     cuvees: list<array{
     *         name: ?string,
     *         type_name: ?string,
     *         unit_label: ?string,
     *         quantity: int,
     *         order_count: int,
     *         total_ttc: int
     *     }>,
     *     delivery_methods: list<array{
     *         name: ?string,
     *         kind: 'address'|'pickup'|'other',
     *         order_count: int,
     *         total_ttc: int
     *     }>,
     *     orders: list<Cart>
     * }
     */
    public function build(array $orders): array
    {
        $orders = array_values($orders);
        usort($orders, fn (Cart $left, Cart $right): int => $this->orderAt($right) <=> $this->orderAt($left));

        $totalSpentTTC = 0;
        $pricedOrderCount = 0;
        $totalQuantity = 0;
        $firstOrderAt = null;
        $lastOrderAt = null;
        $cuvees = [];
        $deliveryMethods = [];

        foreach ($orders as $order) {
            $orderAt = $this->orderAt($order);
            $firstOrderAt = null === $firstOrderAt || $orderAt < $firstOrderAt ? $orderAt : $firstOrderAt;
            $lastOrderAt = null === $lastOrderAt || $orderAt > $lastOrderAt ? $orderAt : $lastOrderAt;

            if (null !== $order->getTotal()) {
                $totalSpentTTC += $order->getTotal();
                ++$pricedOrderCount;
            }

            $deliveryMethod = $order->getDeliveryMethod();
            $deliveryMethodTranslation = $deliveryMethod?->translate('fr', false);
            $deliveryMethodName = $deliveryMethodTranslation instanceof DeliveryMethodTranslation
                ? $deliveryMethodTranslation->getName()
                : null;
            $deliveryName = $this->normalizedLabel($order->getShippingName())
                ?? $this->normalizedLabel($deliveryMethodName);
            $deliveryKind = $deliveryMethod?->isDepot()
                ? 'pickup'
                : ((null !== $order->getDeliveryAddress() || ($order->getShippingTTC() ?? 0) > 0) ? 'address' : 'other');
            $deliveryKey = null !== $deliveryMethod
                ? 'method:'.$deliveryMethod->getId()->toString()
                : $deliveryKind.':'.mb_strtolower((string) $deliveryName);

            if (!isset($deliveryMethods[$deliveryKey])) {
                $deliveryMethods[$deliveryKey] = [
                    'name' => $deliveryName,
                    'kind' => $deliveryKind,
                    'order_count' => 0,
                    'total_ttc' => 0,
                ];
            }
            ++$deliveryMethods[$deliveryKey]['order_count'];
            $deliveryMethods[$deliveryKey]['total_ttc'] += $order->getTotal() ?? 0;

            foreach ($order->getPricing()?->lines ?? [] as $line) {
                if ($line->isPromoGift) {
                    continue;
                }

                $key = $this->cuveeKey($line);
                if (!isset($cuvees[$key])) {
                    $cuvees[$key] = [
                        'name' => $this->normalizedLabel($line->productName) ?? $this->normalizedLabel($line->variantFullName),
                        'type_name' => $this->normalizedLabel($line->typeName),
                        'unit_label' => $this->normalizedLabel($line->unitLabel),
                        'quantity' => 0,
                        'order_count' => 0,
                        'total_ttc' => 0,
                        '_orders' => [],
                    ];
                }

                $quantity = max(0, $line->quantity);
                $cuvees[$key]['quantity'] += $quantity;
                $cuvees[$key]['total_ttc'] += $line->netTTC;
                $cuvees[$key]['_orders'][$order->getId()->toString()] = true;
                $totalQuantity += $quantity;
            }
        }

        foreach ($cuvees as &$cuvee) {
            $cuvee['order_count'] = \count($cuvee['_orders']);
            unset($cuvee['_orders']);
        }
        unset($cuvee);

        usort($cuvees, static function (array $left, array $right): int {
            $quantityComparison = $right['quantity'] <=> $left['quantity'];
            if (0 !== $quantityComparison) {
                return $quantityComparison;
            }

            $revenueComparison = $right['total_ttc'] <=> $left['total_ttc'];
            if (0 !== $revenueComparison) {
                return $revenueComparison;
            }

            return strcmp((string) $left['name'], (string) $right['name']);
        });

        usort($deliveryMethods, static function (array $left, array $right): int {
            $countComparison = $right['order_count'] <=> $left['order_count'];
            if (0 !== $countComparison) {
                return $countComparison;
            }

            return strcmp((string) $left['name'], (string) $right['name']);
        });

        return [
            'summary' => [
                'order_count' => \count($orders),
                'priced_order_count' => $pricedOrderCount,
                'total_spent_ttc' => $totalSpentTTC,
                'average_basket_ttc' => $pricedOrderCount > 0
                    ? (int) round($totalSpentTTC / $pricedOrderCount)
                    : null,
                'total_quantity' => $totalQuantity,
                'first_order_at' => $firstOrderAt,
                'last_order_at' => $lastOrderAt,
            ],
            'cuvees' => array_values($cuvees),
            'delivery_methods' => array_values($deliveryMethods),
            'orders' => $orders,
        ];
    }

    private function orderAt(Cart $order): \DateTimeInterface
    {
        return $order->getCartCheckout()?->getOrderedAt()
            ?? $order->getCartCheckout()?->getPayedAt()
            ?? $order->getCreatedAt();
    }

    private function cuveeKey(CartPricingLine $line): string
    {
        return implode('|', [
            $line->productId ?? mb_strtolower((string) $this->normalizedLabel($line->productName)),
            $line->typeId ?? mb_strtolower((string) $this->normalizedLabel($line->typeName)),
            mb_strtolower((string) $this->normalizedLabel($line->unitLabel)),
        ]);
    }

    private function normalizedLabel(?string $value): ?string
    {
        if (null === $value) {
            return null;
        }

        $value = mb_trim($value);

        return '' !== $value ? $value : null;
    }
}
