<?php

declare(strict_types=1);

namespace MonsieurBiz\SyliusSearchPlugin\Model\Documentable;

use MonsieurBiz\SyliusSearchPlugin\Generated\Model\Taxon as DocumentTaxon;
use MonsieurBiz\SyliusSearchPlugin\Model\Document\Result;
use MonsieurBiz\SyliusSearchPlugin\Model\Document\ResultInterface;
use Sylius\Component\Attribute\Model\AttributeValueInterface;
use Sylius\Component\Core\Model\Channel;
use Sylius\Component\Core\Model\Image;
use Sylius\Component\Core\Model\ProductTaxonInterface;
use Sylius\Component\Core\Model\ProductVariant;
use Sylius\Component\Core\Model\ProductVariantInterface;
use Sylius\Component\Core\Model\TaxonInterface;
use Sylius\Component\Currency\Model\CurrencyInterface;

trait DocumentableProductTrait
{
    public function getDocumentType(): string
    {
        return 'product';
    }

    public function createResult(): ResultInterface
    {
        return new Result();
    }

    public function convertToDocument(string $locale): ResultInterface
    {
        $document = $this->createResult();

        // Document data
        $document->setType($this->getDocumentType());
        $document->setCode($this->getCode());
        $document->setId($this->getId());
        $document->setEnabled($this->isEnabled());
        $document->setInStock($this->getProductHasVariantInStock());
        $document->setSlug($this->getTranslation($locale)->getSlug());

        $document = $this->addImagesInDocument($document);
        $document = $this->addChannelsInDocument($document);
        $document = $this->addPricesInDocument($document);
        $document = $this->addTaxonsInDocument($document, $locale);

        $document->addAttribute('name', 'Name', [$this->getTranslation($locale)->getName()], $locale, 50);
        $document->addAttribute('description', 'Description', [$this->getTranslation($locale)->getDescription()], $locale, 10);
        $document->addAttribute('short_description', 'Short description', [$this->getTranslation($locale)->getShortDescription()], $locale, 10);
        $document->addAttribute('created_at', 'Creation Date', [$this->getCreatedAt()], $locale, 1);

        $document = $this->addAttributesInDocument($document, $locale);

        return $this->addOptionsInDocument($document, $locale);
    }

    protected function addImagesInDocument(ResultInterface $document): ResultInterface
    {
        /** @var Image|bool $image */
        $image = $this->getImages()->first();
        if (false !== $image) {
            $document->setImage($image->getPath());
        }

        return $document;
    }

    protected function addChannelsInDocument(ResultInterface $document): ResultInterface
    {
        /** @var Channel $channel */
        foreach ($this->getChannels() as $channel) {
            $document->addChannel($channel->getCode());
        }

        return $document;
    }

    protected function addPricesInDocument(ResultInterface $document): ResultInterface
    {
        /** @var Channel $channel */
        foreach ($this->getChannels() as $channel) {
            /** @var ProductVariant|null $variant */
            $variant = $this->getCheapestVariantForChannel($channel);

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

            $price = $variant->getChannelPricingForChannel($channel);

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

            /** @var CurrencyInterface $currency */
            foreach ($channel->getCurrencies() as $currency) {
                $document->addPrice($channel->getCode(), $currency->getCode(), $price->getPrice());
                if ($originalPrice = $price->getOriginalPrice()) {
                    $document->addOriginalPrice($channel->getCode(), $currency->getCode(), $originalPrice);
                }
            }
        }

        return $document;
    }

    protected function addTaxonsInDocument(ResultInterface $document, string $locale): ResultInterface
    {
        /** @var TaxonInterface|null $mainTaxon */
        $mainTaxon = $this->getMainTaxon();
        if (null !== $mainTaxon) {
            $taxon = new DocumentTaxon();
            $taxon
                ->setName($mainTaxon->getTranslation($locale)->getName())
                ->setCode($mainTaxon->getCode())
                ->setPosition($mainTaxon->getPosition())
                ->setLevel($mainTaxon->getLevel())
            ;
            $document->setMainTaxon($taxon);
        }

        /** @var ProductTaxonInterface $productTaxon */
        foreach ($this->getProductTaxons() as $productTaxon) {
            $taxon = $productTaxon->getTaxon();
            if (!$taxon instanceof TaxonInterface) {
                continue;
            }

            if (!$taxon->isEnabled()) {
                continue;
            }
            $document->addTaxon(
                $taxon->getCode(),
                $taxon->getTranslation($locale)->getName(),
                $taxon->getPosition(),
                $taxon->getLevel(),
                $productTaxon->getPosition(),
            );
        }

        return $document;
    }

    protected function addAttributesInDocument(ResultInterface $document, string $locale): ResultInterface
    {
        /** @var AttributeValueInterface $attribute */
        foreach ($this->getAttributesByLocale($locale, $locale) as $attribute) {
            $attributeValues = [];
            if (isset($attribute->getAttribute()?->getConfiguration()['choices'])) {
                foreach ($attribute->getValue() as $value) {
                    $attributeValues[] = $attribute->getAttribute()?->getConfiguration()['choices'][$value][$locale];
                }
            } else {
                $attributeValues[] = $attribute->getValue();
            }
            $document->addAttribute($attribute->getCode(), $attribute->getName(), $attributeValues, $attribute->getLocaleCode() ?? $locale, 1);
        }

        return $document;
    }

    /**
     * @param Result $document
     *
     * @return Result
     */
    protected function addOptionsInDocument(ResultInterface $document, string $locale): ResultInterface
    {
        $options = [];
        /** @var ProductVariantInterface $variant */
        foreach ($this->getVariants() as $variant) {
            foreach ($variant->getOptionValues() as $val) {
                if (!isset($options[$val->getOption()?->getCode()])) {
                    $options[$val->getOption()?->getCode()] = [
                        'name' => $val->getOption()?->getTranslation($locale)->getName(),
                        'values' => [],
                    ];
                }
                $options[$val->getOption()?->getCode()]['values'][$val->getCode()] = $val->getTranslation($locale)->getValue();
            }
        }

        foreach ($options as $optionCode => $option) {
            $document->addAttribute($optionCode, $option['name'], array_values($option['values']), $locale, 1);
        }

        return $document;
    }

    private function getCheapestVariantForChannel($channel): ?ProductVariantInterface
    {
        $cheapestVariant = null;
        $cheapestPrice = null;
        $variants = $this->getEnabledVariants();
        foreach ($variants as $variant) {
            $channelPrice = $variant->getChannelPricingForChannel($channel);

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

            if (null === $cheapestPrice || $channelPrice->getPrice() < $cheapestPrice) {
                $cheapestPrice = $channelPrice->getPrice();
                $cheapestVariant = $variant;
            }
        }

        return $cheapestVariant;
    }

    private function getProductHasVariantInStock(): bool
    {
        $variants = $this->getEnabledVariants();
        /** @var ProductVariantInterface $variant */
        foreach ($variants as $variant) {
            if (!$variant->isTracked() || 1 <= ($variant->getOnHand() - $variant->getOnHold())) {
                return true;
            }
        }

        return false;
    }
}
