<?php

declare(strict_types=1);

namespace CpCreation\VitiCore\Shop;

use CpCreation\VitiCore\Cart\Model\Cart;
use CpCreation\VitiCore\Product\Model\ProductType;
use CpCreation\VitiCore\Product\Repository\ProductTypeRepository;
use CpCreation\VitiCore\Shop\DTO\CartQuantityProgressRule;
use CpCreation\VitiCore\Shop\DTO\ShippingContext;
use CpCreation\VitiCore\Shop\Model\ShippingCountry;
use CpCreation\VitiCore\Shop\Model\ShippingCounty;
use CpCreation\VitiCore\Shop\Repository\ShippingCountryRepository;
use CpCreation\VitiCore\Shop\Repository\ShippingCountyRepository;
use CpCreation\VitiCore\Shop\Repository\ShopConfigMultipleRepository;
use Doctrine\ORM\NonUniqueResultException;
use Symfony\Component\Intl\Countries;
use Symfony\Contracts\Translation\TranslatorInterface;

final class CartQuantityProgressResolver
{
    public function __construct(
        private readonly ShippingContextFactory $shippingContextFactory,
        private readonly ShippingCountryRepository $shippingCountryRepository,
        private readonly ShippingCountyRepository $shippingCountyRepository,
        private readonly ProductTypeRepository $productTypeRepository,
        private readonly ShopConfigMultipleRepository $shopConfigMultipleRepository,
        private readonly TranslatorInterface $translator,
    ) {
    }

    /**
     * @return list<CartQuantityProgressRule>
     */
    public function resolve(Cart $cart, ?string $locale = null): array
    {
        if ($cart->getItems()->isEmpty()) {
            return [];
        }

        try {
            $context = $this->shippingContextFactory->createFromCart($cart);
        } catch (\Throwable) {
            return [];
        }

        $country = $this->resolveCountry($context->countryCode, $context->priceGroupId);
        $county = $this->resolveCounty($context->postalCode, $country);
        $typeDefault = $this->productTypeRepository->findDefaultType();
        $rules = [];

        foreach ($this->resolveCartTypes($cart) as $type) {
            foreach ($this->buildTypeRules($context, $type, $county, $country, $typeDefault, $locale) as $rule) {
                $rules[] = $rule;
            }
        }

        foreach ($this->buildVariantLotRules($cart, $locale) as $rule) {
            $rules[] = $rule;
        }

        return $rules;
    }

    /**
     * @return list<CartQuantityProgressRule>
     */
    private function buildTypeRules(
        ShippingContext $context,
        ProductType $type,
        ?ShippingCounty $county,
        ?ShippingCountry $country,
        ?ProductType $typeDefault,
        ?string $locale,
    ): array {
        $typeSlug = $type->getSlug();
        $currentQuantity = $this->normalizeQuantity($context->quantitiesMinCart[$typeSlug] ?? 0.0);
        $minimumQuantity = null;
        $multipleQuantity = $type->getMultipleQuantityCart();
        $sourceLabel = $this->resolveSourceLabel($country, $county, $type, $typeDefault, $locale);
        $rules = [];

        if (null !== $county) {
            try {
                $shopConfigMultiple = $this->shopConfigMultipleRepository->findByQuantityAndByTypeAndByZone(
                    $currentQuantity,
                    $typeSlug,
                    $county->getZone(),
                );
            } catch (NonUniqueResultException) {
                $shopConfigMultiple = null;
            }

            if (null !== $shopConfigMultiple && null !== $shopConfigMultiple->getMultiple()) {
                $multipleQuantity = $shopConfigMultiple->getMultiple();
                $minimumQuantity = $shopConfigMultiple->isMinimum() ? $multipleQuantity : null;
            }
        }

        if (
            null !== $country
            && null !== $typeDefault
            && $type->getId()->toString() === $typeDefault->getId()->toString()
        ) {
            $countryMinimumQuantity = $country->getMinQty();
            $countryMultipleQuantity = $country->getMultiple();

            if (null !== $countryMinimumQuantity) {
                $minimumQuantity = max($minimumQuantity ?? 0, $countryMinimumQuantity);
            }

            if (null !== $countryMultipleQuantity && $countryMultipleQuantity > 1) {
                $multipleQuantity = $this->leastCommonMultiple($multipleQuantity, $countryMultipleQuantity);
            }
        }

        if ($multipleQuantity > 1 || null !== $minimumQuantity) {
            $rules[] = $this->buildRule(
                title: $type->getName(),
                sourceLabel: $sourceLabel,
                requirementLabel: $this->buildRequirementLabel(
                    $minimumQuantity,
                    $multipleQuantity,
                    $locale,
                ),
                currentQuantity: $currentQuantity,
                minimumQuantity: $minimumQuantity,
                multipleQuantity: $multipleQuantity,
                locale: $locale,
            );
        }

        return $rules;
    }

    /**
     * @return list<CartQuantityProgressRule>
     */
    private function buildVariantLotRules(Cart $cart, ?string $locale): array
    {
        $rules = [];

        foreach ($cart->getItems() as $item) {
            $variant = $item->getVariant();

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

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

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

            $rules[] = $this->buildRule(
                title: $variant->getFullName(),
                sourceLabel: '',
                requirementLabel: $this->buildRequirementLabel(
                    minimumQuantity: null,
                    multipleQuantity: $lotQuantity,
                    locale: $locale,
                ),
                currentQuantity: $this->normalizeQuantity($item->getQuantity()),
                minimumQuantity: null,
                multipleQuantity: $lotQuantity,
                locale: $locale,
            );
        }

        return $rules;
    }

    private function buildRule(
        string $title,
        string $sourceLabel,
        string $requirementLabel,
        int $currentQuantity,
        ?int $minimumQuantity,
        int $multipleQuantity,
        ?string $locale,
    ): CartQuantityProgressRule {
        $isComplete = $this->isRuleComplete($currentQuantity, $minimumQuantity, $multipleQuantity);
        $targetQuantity = $this->computeTargetQuantity($currentQuantity, $minimumQuantity, $multipleQuantity);
        $remainingQuantity = max(0, $targetQuantity - $currentQuantity);
        $progressPercent = $isComplete
            ? 100
            : min(99, (int) floor(($currentQuantity / max(1, $targetQuantity)) * 100));

        $statusLabel = $isComplete
            ? $this->translator->trans('cart.progress.status.complete', [], null, $locale)
            : $this->translator->trans(
                'cart.progress.status.remaining',
                [
                    '%remaining%' => $remainingQuantity,
                    '%target%' => $targetQuantity,
                ],
                null,
                $locale,
            );

        return new CartQuantityProgressRule(
            title: $title,
            sourceLabel: $sourceLabel,
            requirementLabel: $requirementLabel,
            statusLabel: $statusLabel,
            currentQuantity: $currentQuantity,
            targetQuantity: $isComplete ? max($currentQuantity, 1) : $targetQuantity,
            progressPercent: $progressPercent,
            isComplete: $isComplete,
        );
    }

    private function buildRequirementLabel(
        ?int $minimumQuantity,
        int $multipleQuantity,
        ?string $locale,
    ): string {
        if (null !== $minimumQuantity && $multipleQuantity > 1) {
            return $this->translator->trans(
                'cart.progress.requirement.minimum_and_multiple',
                [
                    '%minimum%' => $minimumQuantity,
                    '%multiple%' => $multipleQuantity,
                ],
                null,
                $locale,
            );
        }

        if (null !== $minimumQuantity) {
            return $this->translator->trans(
                'cart.progress.requirement.minimum',
                [
                    '%minimum%' => $minimumQuantity,
                ],
                null,
                $locale,
            );
        }

        return $this->translator->trans(
            'cart.progress.requirement.multiple',
            [
                '%multiple%' => $multipleQuantity,
            ],
            null,
            $locale,
        );
    }

    private function resolveSourceLabel(
        ?ShippingCountry $country,
        ?ShippingCounty $county,
        ProductType $type,
        ?ProductType $typeDefault,
        ?string $locale,
    ): string {
        if (
            null !== $country
            && null !== $typeDefault
            && $type->getId()->toString() === $typeDefault->getId()->toString()
            && (null !== $country->getMinQty() || null !== $country->getMultiple())
        ) {
            if (null !== $county) {
                return $this->translator->trans(
                    'cart.progress.source.country_and_zone',
                    ['%country%' => Countries::getName($country->getCode())],
                    null,
                    $locale,
                );
            }

            return $this->translator->trans(
                'cart.progress.source.country',
                ['%country%' => Countries::getName($country->getCode())],
                null,
                $locale,
            );
        }

        if (null !== $county) {
            return $this->translator->trans('cart.progress.source.zone', [], null, $locale);
        }

        return '';
    }

    /**
     * @return list<ProductType>
     */
    private function resolveCartTypes(Cart $cart): array
    {
        $typesBySlug = [];

        foreach ($cart->getItems() as $item) {
            $variant = $item->getVariant();

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

            $type = $variant->getType();
            $typesBySlug[$type->getSlug()] = $type;
        }

        return array_values($typesBySlug);
    }

    private function resolveCountry(?string $countryCode, string $priceGroupId): ?ShippingCountry
    {
        if (null === $countryCode || '' === $countryCode) {
            return null;
        }

        try {
            return $this->shippingCountryRepository->findOneByGroup($countryCode, $priceGroupId);
        } catch (NonUniqueResultException) {
            return null;
        }
    }

    private function resolveCounty(?string $postalCode, ?ShippingCountry $country): ?ShippingCounty
    {
        if (null === $country || !$country->isCounty() || null === $postalCode || '' === $postalCode) {
            return null;
        }

        try {
            return $this->shippingCountyRepository->findDepartmentByCountry(
                mb_substr($postalCode, 0, 2),
                $country->getCode(),
            );
        } catch (NonUniqueResultException) {
            return null;
        }
    }

    private function isRuleComplete(int $currentQuantity, ?int $minimumQuantity, int $multipleQuantity): bool
    {
        if (null !== $minimumQuantity && $currentQuantity < $minimumQuantity) {
            return false;
        }

        if ($multipleQuantity > 1 && 0 !== ($currentQuantity % $multipleQuantity)) {
            return false;
        }

        return true;
    }

    private function computeTargetQuantity(int $currentQuantity, ?int $minimumQuantity, int $multipleQuantity): int
    {
        if ($this->isRuleComplete($currentQuantity, $minimumQuantity, $multipleQuantity)) {
            return max(1, $currentQuantity);
        }

        if ($multipleQuantity > 1) {
            $baseQuantity = max($currentQuantity, $minimumQuantity ?? 0, $multipleQuantity);

            return (int) (ceil($baseQuantity / $multipleQuantity) * $multipleQuantity);
        }

        return max(1, $minimumQuantity ?? 1);
    }

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

    private function leastCommonMultiple(int $left, int $right): int
    {
        $left = max(1, $left);
        $right = max(1, $right);

        return (int) (($left * $right) / $this->greatestCommonDivisor($left, $right));
    }

    private function greatestCommonDivisor(int $left, int $right): int
    {
        while (0 !== $right) {
            $remainder = $left % $right;
            $left = $right;
            $right = $remainder;
        }

        return max(1, $left);
    }
}
