<?php

declare(strict_types=1);

/*
 * 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\Commerce\Catalog\Outbox;

use CpCreation\VitiCore\Cart\Model\Cart;
use CpCreation\VitiCore\Cart\Model\CartCheckout;
use CpCreation\VitiCore\Cart\Model\CartItem;
use CpCreation\VitiCore\Commerce\Catalog\CatalogIdentity;
use CpCreation\VitiCore\Commerce\Catalog\Model\CatalogChange;
use CpCreation\VitiCore\Commerce\Catalog\Model\CatalogChangeType;
use CpCreation\VitiCore\Commerce\Catalog\Model\CatalogResourceType;
use CpCreation\VitiCore\Contact\Model\Contact;
use CpCreation\VitiCore\Locale\Model\Locale;
use CpCreation\VitiCore\Marketing\Model\ProductReview;
use CpCreation\VitiCore\Marketing\Model\ReviewRequestConfiguration;
use CpCreation\VitiCore\Media\Model\Image;
use CpCreation\VitiCore\Product\Model\Product;
use CpCreation\VitiCore\Product\Model\ProductCategory;
use CpCreation\VitiCore\Product\Model\ProductImage;
use CpCreation\VitiCore\Product\Model\ProductPrice;
use CpCreation\VitiCore\Product\Model\ProductPriceGroup;
use CpCreation\VitiCore\Product\Model\ProductType;
use CpCreation\VitiCore\Product\Model\ProductTypeOption;
use CpCreation\VitiCore\Product\Model\ProductVariant;
use CpCreation\VitiCore\Product\Model\ProductVariantImage;
use CpCreation\VitiCore\Product\Model\ProductVariantOption;
use CpCreation\VitiCore\Product\Model\ProductVariantOptionImage;
use CpCreation\VitiCore\Product\Model\Translation\ProductCategoryTranslation;
use CpCreation\VitiCore\Product\Model\Translation\ProductTranslation;
use CpCreation\VitiCore\Product\Model\Translation\ProductTypeOptionTranslation;
use CpCreation\VitiCore\Product\Model\Translation\ProductTypeTranslation;
use CpCreation\VitiCore\Product\Model\Translation\ProductVariantOptionTranslation;
use CpCreation\VitiCore\Shop\Model\ShopConfig;
use CpCreation\VitiCore\Shop\Model\ShopConfigDisableCountry;
use CpCreation\VitiCore\Shop\Model\ShopConfigMultiple;
use CpCreation\VitiCore\Shop\Model\Translation\ShopConfigDisableCountryTranslation;
use CpCreation\VitiCore\Shop\Model\Translation\ShopConfigTranslation;
use Doctrine\Persistence\Proxy;
use Knp\DoctrineBehaviors\Contract\Entity\TranslationInterface;

/**
 * Transforme une mutation Doctrine en notification de catalogue sans requête
 * ni dépendance réseau. Une association non résolue est volontairement ramenée
 * à une réconciliation complète, plus sûre qu'une notification ciblée erronée.
 */
final readonly class CatalogChangeClassifier
{
    public function __construct(private CatalogIdentity $identity) {}

    public function classify(object $entity, ?CatalogMutationContext $context = null): ?CatalogChangeDraft
    {
        $context ??= CatalogMutationContext::unspecified();

        if ($entity instanceof CatalogChange) {
            return null;
        }

        if ($entity instanceof Product) {
            return $this->product($entity, $context->isProductRemoval());
        }

        if ($entity instanceof ProductReview) {
            return $this->productOrReconciliation($entity->getProduct());
        }

        if ($entity instanceof ReviewRequestConfiguration) {
            return $this->reconciliation();
        }

        if ($entity instanceof Contact) {
            return new CatalogChangeDraft(
                CatalogChangeType::SELLER_CHANGED,
                CatalogResourceType::SELLER,
                $this->identity->sellerId(),
            );
        }

        if ($entity instanceof ProductTranslation) {
            return $this->productOrReconciliation($this->translationOwner($entity));
        }

        if ($entity instanceof ProductVariant) {
            return $this->productOrReconciliation($this->productFromVariant($entity));
        }

        if ($entity instanceof ProductPrice) {
            return $this->productOrReconciliation($this->productFromVariant($entity->getVariant()));
        }

        if ($entity instanceof ProductImage) {
            return $this->productOrReconciliation($entity->getProduct());
        }

        if ($entity instanceof ProductVariantImage) {
            return $this->productOrReconciliation($this->productFromVariant($entity->getVariant()));
        }

        if ($entity instanceof ProductVariantOption) {
            return $this->productOrReconciliation($this->productFromOption($entity));
        }

        if ($entity instanceof ProductVariantOptionTranslation) {
            $option = $this->translationOwner($entity);

            return $this->productOrReconciliation(
                $option instanceof ProductVariantOption ? $this->productFromOption($option) : null,
            );
        }

        if ($entity instanceof ProductVariantOptionImage) {
            return $this->productOrReconciliation($this->productFromOption($entity->getOption()));
        }

        if ($entity instanceof CartItem) {
            return $this->cartItem($entity, $context);
        }

        if ($entity instanceof Cart) {
            return $this->cart($entity, $context);
        }

        if ($entity instanceof CartCheckout) {
            return $this->cartCheckout($entity, $context);
        }

        if ($this->requiresReconciliation($entity)) {
            return $this->reconciliation();
        }

        return null;
    }

    private function cartItem(CartItem $item, CatalogMutationContext $context): ?CatalogChangeDraft
    {
        if ($context->isUnspecified()) {
            return $this->productOrReconciliation($this->productFromVariant($item->getVariant()));
        }

        if (
            !$context->isInsertion()
            && !$context->isDeletion()
            && !$context->changesAny(['quantity', 'variant', 'cart'])
        ) {
            return null;
        }

        $oldCart = $context->oldValue('cart');
        if (!$this->cartAffectsStock($item->getCart()) && (!$oldCart instanceof Cart || !$this->cartAffectsStock($oldCart))) {
            return null;
        }

        return $this->reconciliation();
    }

    private function cart(Cart $cart, CatalogMutationContext $context): ?CatalogChangeDraft
    {
        if ($context->isUnspecified()) {
            return $this->reconciliation();
        }

        if (!$context->isDeletion() && !$context->changesAny(['expiresAt', 'deletedAt', 'items'])) {
            return null;
        }

        return $this->cartAffectsStock($cart) ? $this->reconciliation() : null;
    }

    private function cartCheckout(CartCheckout $checkout, CatalogMutationContext $context): ?CatalogChangeDraft
    {
        if ($context->isUnspecified()) {
            return $this->reconciliation();
        }

        if ($context->isDeletion()) {
            return $this->reconciliation();
        }

        if ($context->isInsertion()) {
            return $this->checkoutAffectsStock($checkout) ? $this->reconciliation() : null;
        }

        return $context->changesAny(['payedAt', 'initiatedAt', 'orderedAt', 'disabled', 'deletedAt', 'cart'])
            ? $this->reconciliation()
            : null;
    }

    private function cartAffectsStock(?Cart $cart): bool
    {
        if (!$cart instanceof Cart) {
            return false;
        }

        if ($this->isUninitializedProxy($cart)) {
            return true;
        }

        return $this->checkoutAffectsStock($cart->getCartCheckout());
    }

    private function checkoutAffectsStock(?CartCheckout $checkout): bool
    {
        if (!$checkout instanceof CartCheckout) {
            return false;
        }

        if ($this->isUninitializedProxy($checkout)) {
            return true;
        }

        return !$checkout->isDisabled()
            && !$checkout->isDeleted()
            && (
                null !== $checkout->getPayedAt()
                || null !== $checkout->getInitiatedAt()
                || null !== $checkout->getOrderedAt()
            );
    }

    private function productFromVariant(?ProductVariant $variant): ?Product
    {
        if (!$variant instanceof ProductVariant || $this->isUninitializedProxy($variant)) {
            return null;
        }

        return $variant->getProduct();
    }

    private function productFromOption(?ProductVariantOption $option): ?Product
    {
        if (!$option instanceof ProductVariantOption || $this->isUninitializedProxy($option)) {
            return null;
        }

        return $this->productFromVariant($option->getVariant());
    }

    private function isUninitializedProxy(object $entity): bool
    {
        return $entity instanceof Proxy && !$entity->__isInitialized();
    }

    private function product(Product $product, bool $removed): CatalogChangeDraft
    {
        return new CatalogChangeDraft(
            $removed ? CatalogChangeType::PRODUCT_REMOVED : CatalogChangeType::PRODUCT_CHANGED,
            CatalogResourceType::PRODUCT,
            $this->identity->productId($product->getId()),
        );
    }

    private function productOrReconciliation(?Product $product): CatalogChangeDraft
    {
        return $product instanceof Product ? $this->product($product, false) : $this->reconciliation();
    }

    private function reconciliation(): CatalogChangeDraft
    {
        return new CatalogChangeDraft(
            CatalogChangeType::RECONCILIATION_REQUIRED,
            CatalogResourceType::CATALOG,
            null,
        );
    }

    private function translationOwner(TranslationInterface $translation): ?object
    {
        try {
            return $translation->getTranslatable();
        } catch (\Error) {
            return null;
        }
    }

    private function requiresReconciliation(object $entity): bool
    {
        return $entity instanceof ProductCategory
            || $entity instanceof ProductCategoryTranslation
            || $entity instanceof ProductType
            || $entity instanceof ProductTypeTranslation
            || $entity instanceof ProductTypeOption
            || $entity instanceof ProductTypeOptionTranslation
            || $entity instanceof ProductPriceGroup
            || $entity instanceof Image
            || $entity instanceof Locale
            || $entity instanceof ShopConfig
            || $entity instanceof ShopConfigTranslation
            || $entity instanceof ShopConfigDisableCountry
            || $entity instanceof ShopConfigDisableCountryTranslation
            || $entity instanceof ShopConfigMultiple;
    }
}
