<?php

declare(strict_types=1);

/*
 * This file is part of the Serenity 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\Shop;

use CpCreation\VitiCore\Cart\Model\CartItem;
use CpCreation\VitiCore\Cart\Repository\CartRepository;
use CpCreation\VitiCore\Product\Model\ProductType;
use CpCreation\VitiCore\Shop\DTO\ShippingContext;
use CpCreation\VitiCore\Shop\Model\ShippingCountry;
use CpCreation\VitiCore\Shop\Model\ShippingCounty;
use CpCreation\VitiCore\Shop\Repository\ShopConfigMultipleRepository;
use Symfony\Component\Intl\Countries;
use Symfony\Contracts\Translation\TranslatorInterface;

final class ShippingValidator
{
    public function __construct(
        private CartRepository $cartRepository,
        private ShopConfigMultipleRepository $shopConfigMultipleRepository,
        private TranslatorInterface $translator,
    ) {
    }

    /**
     * @param list<ProductType> $types
     *
     * @throws \Exception
     */
    public function validate(
        ShippingContext $context,
        ?ShippingCountry $country,
        ?ShippingCounty $county,
        ?ProductType $typeDefault,
        array $types,
        ?string $locale = null,
    ): void {
        $this->validateCartNotEmpty($context, $locale);
        $this->validateItemsStockAndMaxQuantity($context, $locale);
        $this->validateVariantLots($context, $locale);
        $this->validateCountryAndPostalCode($context, $country, $locale);

        if (null !== $typeDefault) {
            $this->validateTypeQuantities(
                $context,
                $country,
                $county,
                $typeDefault,
                $types,
                $locale,
            );

            $this->validateCountryMinimums(
                $context,
                $country,
                $typeDefault,
                $types,
                $locale,
            );

            $this->validateDeliveryMethod(
                $context,
                $county,
                $locale,
            );
        }
    }

    /**
     * @throws \Exception
     */
    private function validateCartNotEmpty(ShippingContext $context, ?string $locale): void
    {
        if ($context->cart->getItems()->count() < 1) {
            throw new \Exception($this->translator->trans('shop.empty_cart', [], null, $locale));
        }
    }

    /**
     * @throws \Exception
     */
    private function validateItemsStockAndMaxQuantity(ShippingContext $context, ?string $locale): void
    {
        $messages = '';

        /** @var CartItem $item */
        foreach ($context->cart->getItems() as $item) {
            $variant = $item->getVariant();

            if (null === $variant) {
                continue;
            }

            $type = $variant->getType();

            if (
                $item->getQuantity() > 0
                && $type->getMaximumQuantityCart() > 0
                && $item->getQuantity() > $type->getMaximumQuantityCart()
            ) {
                $messages .= $this->translator->trans(
                    'shop.error.quantity_type_override_max',
                    [
                        '%quantity%' => $type->getMaximumQuantityCart(),
                        '%product%' => $variant->getProduct()->getFullName(),
                        '%type%' => $type->getName(),
                    ],
                    null,
                    $locale
                ).'<br/>';
            }

            $saleSinceStockAt = $this->cartRepository->stockSaleSinceStockAt($variant)[0]['quantitySale'] ?? 0;

            if ($saleSinceStockAt && (($item->getQuantity() + $saleSinceStockAt) > $variant->getStock())) {
                $messages .= $this->translator->trans(
                    'shop.error.quantity_type_sold_out',
                    [
                        '%stock%' => $variant->getStock() - $saleSinceStockAt,
                        '%quantity%' => $item->getQuantity(),
                        '%product%' => $variant->getProduct()->getFullName(),
                        '%type%' => $type->getName(),
                    ],
                    null,
                    $locale
                ).'<br/>';
            }
        }

        if ('' !== $messages) {
            throw new \Exception($messages);
        }
    }

    /**
     * @throws \Exception
     */
    private function validateVariantLots(ShippingContext $context, ?string $locale): void
    {
        $messages = '';

        /** @var array<string, array{name: string, lot: int, quantity: int}> $variants */
        $variants = [];

        /** @var CartItem $item */
        foreach ($context->cart->getItems() as $item) {
            $variant = $item->getVariant();

            if (null === $variant) {
                continue;
            }

            $lotQuantity = max(1, $variant->getLot() ?? 1);

            if ($lotQuantity <= 1) {
                continue;
            }

            $variantId = $variant->getId()->toString();

            if (!isset($variants[$variantId])) {
                $variants[$variantId] = [
                    'name' => $variant->getFullName(),
                    'lot' => $lotQuantity,
                    'quantity' => 0,
                ];
            }

            $variants[$variantId]['quantity'] += $item->getQuantity();
        }

        foreach ($variants as $variantData) {
            if (0 === ($variantData['quantity'] % $variantData['lot'])) {
                continue;
            }

            $messages .= $this->translator->trans(
                'shop.error.quantity_variant_lot',
                [
                    '%name%' => $variantData['name'],
                    '%quantity%' => $variantData['lot'],
                ],
                null,
                $locale
            ).'<br/>';
        }

        if ('' !== $messages) {
            throw new \Exception($messages);
        }
    }

    /**
     * @throws \Exception
     */
    private function validateCountryAndPostalCode(
        ShippingContext $context,
        ?ShippingCountry $country,
        ?string $locale,
    ): void {
        $deliveryMethod = $context->deliveryMethod;
        $usesMethodShippingPrices = null !== $deliveryMethod && $this->usesMethodShippingPrices($deliveryMethod);

        if (
            (null === $country && null === $deliveryMethod)
            || (null === $country && $usesMethodShippingPrices)
        ) {
            throw new \Exception($this->translator->trans('shop.error.error_country', [], null, $locale));
        }

        if (
            null !== $country
            && $country->isCounty()
            && (null === $context->postalCode || '' === $context->postalCode)
            && (null === $deliveryMethod || $usesMethodShippingPrices)
        ) {
            throw new \Exception($this->translator->trans('shop.error.postal_code', [], null, $locale));
        }
    }

    /**
     * @param list<ProductType> $types
     *
     * @throws \Exception
     */
    private function validateTypeQuantities(
        ShippingContext $context,
        ?ShippingCountry $country,
        ?ShippingCounty $county,
        ProductType $typeDefault,
        array $types,
        ?string $locale,
    ): void {
        $deliveryMethod = $context->deliveryMethod;

        if (null !== $deliveryMethod && $deliveryMethod->isMultipleVariant()) {
            return;
        }

        $messages = '';

        foreach ($types as $type) {
            $typeSlug = $type->getSlug();
            $typeMultipleQuantityCart = $type->getMultipleQuantityCart();

            if (null !== $country && null !== $county) {
                $shopConfigMultiple = $this->shopConfigMultipleRepository->findByQuantityAndByTypeAndByZone(
                    $this->normalizeQuantityForValidation($context->quantitiesMinCart[$typeSlug] ?? 0.0),
                    $typeSlug,
                    $county->getZone()
                );

                if (null !== $shopConfigMultiple) {
                    $typeMultipleQuantityCart = $shopConfigMultiple->getMultiple();
                }
            } else {
                $shopConfigMultiple = null;
            }

            if (
                $type->getOrderWithaMinimumEquivalency()
                && $type->getOrderWithaMinimumEquivalency() > $context->quantityAllAbsolute
            ) {
                $messages .= $this->translator->trans(
                    'shop.error.quantity_type_min_equivalency',
                    [
                        '%min%' => $type->getOrderWithaMinimumEquivalency(),
                        '%type%' => $type->getName(),
                    ],
                    null,
                    $locale
                ).'<br/>';
            }

            $quantityMinCart = $this->normalizeQuantityForValidation(
                $context->quantitiesMinCart[$typeSlug] ?? 0.0
            );

            if ($typeMultipleQuantityCart > 0 && ($quantityMinCart % $typeMultipleQuantityCart) > 0) {
                if (null !== $shopConfigMultiple) {
                    if ($shopConfigMultiple->isMinimum()) {
                        $messages .= $this->translator->trans(
                            'shop.error.quantity_type_override_min',
                            [
                                '%quantity%' => $typeMultipleQuantityCart,
                                '%type%' => $type->getName(),
                            ],
                            null,
                            $locale
                        ).'<br/>';
                    } else {
                        $messages .= $this->translator->trans(
                            'shop.error.quantity_type_override',
                            [
                                '%quantity%' => $typeMultipleQuantityCart,
                                '%min%' => $shopConfigMultiple->getQtyOf(),
                                '%max%' => $shopConfigMultiple->getQtyAt(),
                                '%type%' => $type->getName(),
                            ],
                            null,
                            $locale
                        ).'<br/>';
                    }
                } else {
                    $messages .= $this->translator->trans(
                        'shop.error.quantity_type',
                        [
                            '%quantity%' => $typeMultipleQuantityCart,
                            '%type%' => $type->getName(),
                        ],
                        null,
                        $locale
                    ).'<br/>';
                }
            }
        }

        if ('' !== $messages) {
            throw new \Exception($messages);
        }
    }

    /**
     * @throws \Exception
     */
    private function validateCountryMinimums(
        ShippingContext $context,
        ?ShippingCountry $country,
        ProductType $typeDefault,
        array $types,
        ?string $locale,
    ): void {
        if (null === $country || null !== $context->deliveryMethod) {
            return;
        }

        $defaultTypeIsPresentInCart = false;

        foreach ($types as $type) {
            if ($type->getSlug() === $typeDefault->getSlug()) {
                $defaultTypeIsPresentInCart = true;

                break;
            }
        }

        if (!$defaultTypeIsPresentInCart) {
            return;
        }

        if ($country->getMinQty() && $country->getMultiple()) {
            $quantity = $this->normalizeQuantityForValidation(
                $context->quantitiesMinCart[$typeDefault->getSlug()] ?? 0.0
            );

            if ((($quantity % $country->getMultiple()) > 0) || ($quantity < $country->getMinQty())) {
                $message = $this->translator->trans(
                    'shop.error.error_country_qty',
                    [
                        '%multiple%' => $country->getMultiple(),
                        '%quantity%' => $country->getMinQty(),
                        '%type%' => $typeDefault->getName(),
                        '%country%' => Countries::getName($country->getCode()),
                    ],
                    null,
                    $locale
                ).'<br/>';

                throw new \Exception($message);
            }
        }
    }

    /**
     * @throws \Exception
     */
    private function validateDeliveryMethod(
        ShippingContext $context,
        ?ShippingCounty $county,
        ?string $locale,
    ): void {
        $deliveryMethod = $context->deliveryMethod;

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

        $messages = '';
        $errorProducts = [];
        $errorQuantityType = [];

        /** @var CartItem $item */
        foreach ($context->cart->getItems() as $item) {
            $variant = $item->getVariant();

            if (null === $variant) {
                continue;
            }

            $type = $variant->getType();

            if ($deliveryMethod->isMultipleVariant() && $type->getMultipleQuantityDepot() > 0) {
                if (($item->getQuantity() % $type->getMultipleQuantityDepot()) > 0) {
                    $errorQuantityType[] = [
                        'type' => $type->getName(),
                        'quantity' => $type->getMultipleQuantityDepot(),
                    ];
                }
            }

            if ($deliveryMethod->getProductVariants()->count() > 0 && !$deliveryMethod->getProductVariants()->contains($variant)) {
                $errorProducts[] = $variant->getName();
            }
        }

        if ([] !== $errorProducts) {
            $messages .= $this->translator->trans(
                'shop.error.error_product_delivery_method',
                ['%products%' => implode(', ', $errorProducts)],
                null,
                $locale
            ).'<br/>';
        } elseif ([] !== $errorQuantityType) {
            foreach ($errorQuantityType as $errorQuantity) {
                $messages .= $this->translator->trans(
                    'shop.error.error_product_delivery_method_quantity_variant',
                    [
                        '%type%' => $errorQuantity['type'],
                        '%quantity%' => $errorQuantity['quantity'],
                    ],
                    null,
                    $locale
                ).'<br/>';
            }
        }

        if ($deliveryMethod->getQtyAt() > $this->normalizeQuantityForValidation($context->quantityAllAbsolute)) {
            $messages .= $this->translator->trans('shop.delivery_method.error_qty', [], null, $locale).'<br/>';
        }

        if (0 === $deliveryMethod->getShippingPrices()->count()) {
            if ($deliveryMethod->isViewCounties() && (null === $county || !$deliveryMethod->getCounties()->contains($county))) {
                $messages .= $this->translator->trans('shop.delivery_method.error_county', [], null, $locale).'<br/>';
            }
        }

        if ('' !== $messages) {
            throw new \Exception($messages);
        }
    }

    private function normalizeQuantityForValidation(float|int $quantity): int
    {
        return max(0, (int) ceil((float) $quantity));
    }

    private function usesMethodShippingPrices(\CpCreation\VitiCore\Shop\Model\DeliveryMethod $deliveryMethod): bool
    {
        return $deliveryMethod->isEnabledShippingPrice() && $deliveryMethod->getShippingPrices()->count() > 0;
    }
}
