<?php

namespace KupShop\GraphQLBundle\ApiShared\Types\Product;

use KupShop\CatalogBundle\ProductList\ProductCollection;
use KupShop\GraphQLBundle\ApiAdmin\Annotation\Module;
use KupShop\GraphQLBundle\ApiAdmin\Util\Catalog\SectionUtil;
use KupShop\GraphQLBundle\ApiShared\ApiUtil;
use KupShop\GraphQLBundle\ApiShared\Types\Content\EditableContent;
use KupShop\GraphQLBundle\ApiShared\Types\Enums\PhotoSizeEnum;
use KupShop\GraphQLBundle\ApiShared\Types\Enums\ProductVisibilityEnum;
use KupShop\GraphQLBundle\ApiShared\Types\Price\Price;
use KupShop\GraphQLBundle\ApiShared\Types\Section\Input\SectionFilterInput;
use KupShop\GraphQLBundle\ApiShared\Types\Section\Section;
use KupShop\GraphQLBundle\ApiShared\Types\Seo;
use KupShop\GraphQLBundle\ApiShared\Types\Store\StoreItem;
use KupShop\KupShopBundle\Context\LanguageContext;
use KupShop\KupShopBundle\Util\Compat\ServiceContainer;
use KupShop\KupShopBundle\Util\Contexts;
use KupShop\KupShopBundle\Util\Functional\Mapping;
use KupShop\KupShopBundle\Util\Price\PriceUtil;
use KupShop\KupShopBundle\Util\Price\ProductPrice;
use KupShop\KupShopBundle\Util\StringUtil;
use Query\Operator;
use Symfony\Component\Routing\Router;
use TheCodingMachine\GraphQLite\Annotations\Field;
use TheCodingMachine\GraphQLite\Annotations\Type;

/**
 * Produkt.
 *
 * @Type()
 */
class ProductBase
{
    protected \Product $product;

    private ?SectionUtil $sectionUtil = null;

    public function __construct(\Product $product, public ProductCollection $collection)
    {
        $this->product = $product;
    }

    /**
     * Interní ID.
     *
     * @Field()
     */
    public function getId(): int
    {
        return $this->product->id;
    }

    /**
     * ID varianty.
     *
     * @Field()
     */
    public function getVariationId(): ?int
    {
        return $this->product->variationId ? (int) $this->product->variationId : null;
    }

    /**
     * URL adresa.
     *
     * @Field()
     */
    public function getUrl(): string
    {
        return path(
            'products',
            [
                'id' => $this->product->id,
                'name' => StringUtil::slugify($this->product->title),
            ],
            Router::ABSOLUTE_URL
        );
    }

    /**
     * Kód.
     *
     * @Field()
     */
    public function getCode(): ?string
    {
        return $this->product->code;
    }

    /**
     * EAN-13.
     *
     * @Field()
     */
    public function getEan(): ?string
    {
        return !empty($this->product->ean) ? formatEAN($this->product->ean) : null;
    }

    #[Field(description: 'EAN bez formátování')]
    public function getEanRaw(): ?string
    {
        return !empty($this->product->ean) ? (string) $this->product->ean : null;
    }

    /**
     * Počet kusů skladem.
     *
     * @Field()
     */
    public function getInStore(): float
    {
        return (float) $this->product->inStore;
    }

    #[Field(prefetchMethod: 'prefetchInStoreSuppliers', description: 'Součet počtu kusů skladem u všech dodavatelů.')]
    public function getInStoreSuppliers(array $_): float
    {
        return (float) $this->product->in_store_suppliers;
    }

    public function prefetchInStoreSuppliers(array $products): array
    {
        $this->collection->fetchProductOfSuppliersInStore();

        return [];
    }

    /**
     * @return StoreItem[]
     */
    #[Field(prefetchMethod: 'prefetchStores', description: 'Skladovost na jednotlivých skladech.')]
    public function getStores(array $_): array
    {
        $result = [];
        foreach ($this->product->storesInStore ?? [] as $storeId => $storeItem) {
            $storeItem['id_store'] = $storeId;

            $result[] = new StoreItem($storeItem);
        }

        return $result;
    }

    public function prefetchStores(array $products): array
    {
        $this->collection->fetchStoresInStore();

        return [];
    }

    /**
     * Název produktu.
     *
     * @Field()
     */
    public function getTitle(): string
    {
        return $this->product->title;
    }

    /**
     * Název varianty.
     *
     * @Field()
     */
    public function getVariationTitle(): ?string
    {
        return $this->product->variationTitle ?? null;
    }

    /**
     * Krátký popis produktu.
     *
     * @Field()
     */
    public function getDescription(): string
    {
        return $this->product->descr;
    }

    /**
     * Dlouhý popis produktu (HTML).
     *
     * @Field()
     */
    public function getLongDescription(): string
    {
        return $this->product->longDescr;
    }

    #[Field(description: 'Rozšiřující popis.')]
    public function getAdditionalDescription(): string
    {
        return $this->product->parameters;
    }

    #[Field(description: 'Popis+ - editovatelný obsah.', prefetchMethod: 'prefetchProductsBlocks')]
    #[Module(\Modules::PRODUCTS, \Modules::SUB_DESCR_PLUS)]
    public function getDescriptionPlus(array $data): EditableContent
    {
        return new EditableContent(
            $this->product->id_block ?? null,
            $data[$this->product->id_block] ?? [],
            'products',
            $this->product->id
        );
    }

    public function prefetchProductsBlocks(array $products): array
    {
        return ApiUtil::fetchBlocksByRootIds(
            array_filter(array_map(fn (ProductBase $product) => $product->getBlockId(), $products))
        );
    }

    public function getBlockId(): ?int
    {
        return $this->product->id_block;
    }

    /**
     * Sleva.
     *
     * @Field()
     */
    public function getDiscount(): float
    {
        return $this->product->discount->asFloat();
    }

    /**
     * Výrobce.
     */
    #[Field(prefetchMethod: 'prefetchProducer', description: 'Výrobce.')]
    public function getProducer(array $_): ?Producer
    {
        $producer = $this->product->fetchProducer();

        return $producer ? new Producer($producer) : null;
    }

    public function prefetchProducer(array $products): array
    {
        $this->collection->fetchProducers();

        return [];
    }

    /**
     * Cena.
     *
     * @Field()
     */
    public function getPrice(): Price
    {
        return new Price(
            $this->product->getProductPrice()
        );
    }

    #[Field(description: 'Cena před slevou.')]
    public function getPriceWithoutDiscount(): Price
    {
        return new Price(
            $this->product->getProductPrice()->getPriceWithoutDiscount()
        );
    }

    #[Field(description: 'Nákupní cena')]
    #[Module(\Modules::PRODUCTS, \Modules::SUB_PRICE_BUY)]
    public function getPriceBuy(): ?Price
    {
        static $priceUtil;

        if (!$priceUtil) {
            $priceUtil = ServiceContainer::getService(PriceUtil::class);
        }

        $priceBuy = $this->product->price_buy;

        if (!$priceBuy) {
            return null;
        }

        if (!($priceBuy instanceof ProductPrice)) {
            $priceBuy = $priceUtil->getFromPriceArray($priceBuy);
        }

        return new Price($priceBuy, false);
    }

    #[Field(prefetchMethod: 'prefetchVisible', description: 'Viditelný.')]
    public function getVisible(array $data): bool
    {
        // when hideNotTranslated is enabled and product is not translated then visible returns false, because product is not visible
        if (!isset($data[$this->product->id])) {
            return false;
        }

        return $this->product->isVisible() && !$this->product->isOld();
    }

    public function prefetchVisible(array $products): array
    {
        $language = Contexts::get(LanguageContext::class)->getActiveId();

        $dbcfg = \Settings::createFromDB($language);

        $isHideNotTranslatedEnabled = Contexts::get(LanguageContext::class)->getDefaultId() !== $language && ($dbcfg->hide_not_translated_objects['products'] ?? 'N') === 'Y';

        // hide not translated is not enabled, so return true for all products
        if (!findModule(\Modules::TRANSLATIONS) || !$isHideNotTranslatedEnabled) {
            return Mapping::mapKeys($products, fn ($k, $v) => [$v->getId(), true]);
        }

        $ids = array_map(fn (ProductBase $x) => $x->getId(), $products);

        // hide not translated is enabled, so load products that has any translation
        return sqlQueryBuilder()
            ->select('id_product, id')
            ->from('products_translations')
            ->andWhere(Operator::inIntArray($ids, 'id_product'))
            ->andWhere(Operator::equals(['id_language' => $language]))
            ->execute()->fetchAllAssociativeIndexed();
    }

    /**
     * Viditelnost.
     *
     * @Field()
     */
    public function getVisibility(): ProductVisibilityEnum
    {
        return ProductVisibilityEnum::from($this->product->visible);
    }

    /**
     * Váha v kg.
     *
     * @Field()
     */
    public function getWeight(): ?float
    {
        return $this->product->weight ? (float) $this->product->weight : null;
    }

    /**
     * Šířka v cm.
     *
     * @Field()
     */
    public function getWidth(): ?float
    {
        return $this->product->width ? (float) $this->product->width : null;
    }

    /**
     * Výška v cm.
     *
     * @Field()
     */
    public function getHeight(): ?float
    {
        return $this->product->height ? (float) $this->product->height : null;
    }

    /**
     * Hloubka v cm.
     *
     * @Field()
     */
    public function getDepth(): ?float
    {
        return $this->product->depth ? (float) $this->product->depth : null;
    }

    /**
     * SEO.
     *
     * @Field()
     */
    public function getSeo(): Seo
    {
        return new Seo(
            [
                'title' => $this->product->meta_title,
                'description' => $this->product->meta_description,
                'keywords' => $this->product->meta_keywords,
            ]
        );
    }

    /**
     * Jednotka produktu.
     *
     * @Field()
     *
     * @Module("PRODUCTS", submodule="UNITS")
     */
    public function getUnit(): ProductUnit
    {
        return new ProductUnit($this->product->unit ?? []);
    }

    #[Field(prefetchMethod: 'prefetchDeliveryTime', description: 'Dostupnost.')]
    public function getDeliveryTime(array $_): DeliveryTime
    {
        return new DeliveryTime((int) $this->product->deliveryTime);
    }

    public function prefetchDeliveryTime(array $products): array
    {
        $this->collection->fetchDeliveryText();

        return [];
    }

    /**
     * @return ProductLink[]
     */
    #[Field(prefetchMethod: 'prefetchLinks', description: 'Odkazy k produktu.')]
    public function getLinks(array $_): array
    {
        return ApiUtil::wrapItems($this->product->links ?? [], ProductLink::class);
    }

    public function prefetchLinks(array $products)
    {
        $this->collection->fetchLinks();

        return [];
    }

    /**
     * @return Product[]
     */
    #[Field(prefetchMethod: 'prefetchProductsRelated', description: 'Související produkty.')]
    #[Module(\Modules::PRODUCTS_RELATED)]
    public function getRelatedProducts(array $_): ProductCollection
    {
        $collection = new ProductCollection();

        foreach ($this->product->products_related ?? [] as $relatedProduct) {
            $collection->set($relatedProduct['id'], new Product($relatedProduct, $collection));
        }

        return $collection;
    }

    public function prefetchProductsRelated(array $products): array
    {
        $this->collection->fetchProductsRelated();

        return [];
    }

    /**
     * @return Product[]
     */
    #[Field(prefetchMethod: 'prefetchCollectionProducts', description: 'Produkty v kolekci.')]
    #[Module(\Modules::PRODUCTS_COLLECTIONS)]
    public function getCollectionProducts(array $_): ProductCollection
    {
        $collection = new ProductCollection();

        foreach ($this->product->collections ?? [] as $collectionProduct) {
            $collection->set($collectionProduct['id'], new Product($collectionProduct, $collection));
        }

        return $collection;
    }

    public function prefetchCollectionProducts(array $products): array
    {
        $this->collection->fetchCollections();

        return [];
    }

    /**
     * @return ProductParameter[]
     */
    #[Field(prefetchMethod: 'prefetchParameters', description: 'Parametry.')]
    public function getParameters(array $_): array
    {
        // wrap product parameters
        $parameters = [];
        foreach ($this->product->param ?? [] as $item) {
            $parameter = new \Parameter();
            $parameter->createFromArray($item);

            $parameters[] = new ProductParameter($parameter, $item['values'] ?? []);
        }

        return $parameters;
    }

    public function prefetchParameters(array $products): array
    {
        $this->collection->fetchParameters();

        return [];
    }

    /**
     * @return Variation[]
     */
    #[Field(prefetchMethod: 'prefetchVariations', description: 'Varianty.')]
    public function getVariations(array $_): array
    {
        $variations = [];
        foreach ($this->product->variations ?? [] as $variation) {
            $variations[] = new Variation($this->product, $variation, $this->collection);
        }

        return $variations;
    }

    public function prefetchVariations(array $products): array
    {
        $this->collection->fetchVariations(true, false);

        return [];
    }

    /**
     * @return ProductPriceList[]
     */
    #[Field(prefetchMethod: 'prefetchPriceLists', description: 'Ceníky.')]
    #[Module(\Modules::PRICELISTS)]
    public function getPriceLists(array $_): array
    {
        return ApiUtil::wrapItems(
            $this->product->pricelists ?? [],
            ProductPriceList::class
        );
    }

    public function prefetchPriceLists(array $products): array
    {
        $this->collection->fetchPricelists();

        return [];
    }

    /**
     * @return Label[]
     */
    #[Field(prefetchMethod: 'prefetchLabels', description: 'Štítky.')]
    #[Module(\Modules::LABELS)]
    public function getLabels(array $_): array
    {
        return ApiUtil::wrapItems(
            $this->product->labels ?? [],
            Label::class
        );
    }

    public function prefetchLabels(array $products): array
    {
        $this->collection->fetchProductLabels();

        return [];
    }

    /**
     * @return Section[]
     */
    #[Field(prefetchMethod: 'prefetchSections', description: 'Sekce, ve kterých je produkt zařazen.')]
    public function getSections(array $_, ?SectionFilterInput $filter = null): array
    {
        return ApiUtil::wrapItems(
            $this->getSectionUtil()->filterSections($this->product->fetchSections(), $filter),
            Section::class
        );
    }

    public function prefetchSections(array $products): array
    {
        $this->collection->fetchSections();

        return [];
    }

    /**
     * @return Photo[]
     */
    #[Field(prefetchMethod: 'prefetchPhotos', description: 'Obrázky.')]
    public function getPhotos(array $_, ?PhotoSizeEnum $size): array
    {
        if ($this->product->image) {
            array_unshift($this->product->photos, array_merge($this->product->image, ['show_in_lead' => 'Y']));
        }

        return ApiUtil::wrapItems(
            $this->product->photos,
            Photo::class
        );
    }

    public function prefetchPhotos(array $products): array
    {
        $this->collection->fetchPhotos(PhotoSizeEnum::PHOTO_DETAIL->value, ['Y', 'N'], true);

        return [];
    }

    protected function getPhotoSizeFromEnum(?PhotoSizeEnum $size): int
    {
        return $size ? $size->value : PhotoSizeEnum::PHOTO_DETAIL->value;
    }

    private function getSectionUtil(): SectionUtil
    {
        if (!$this->sectionUtil) {
            $this->sectionUtil = ServiceContainer::getService(SectionUtil::class);
        }

        return $this->sectionUtil;
    }

    #[Field(prefetchMethod: 'prefetchBonusPoints', description: 'Bonusové body')]
    public function getBonusPoints(array $_): ?float
    {
        return $this->product->bonus_points?->asFloat();
    }

    public function prefetchBonusPoints(array $products): array
    {
        $this->collection->fetchBonusPoints();

        return [];
    }

    /** @return ProductBatch[] */
    #[Module(\Modules::PRODUCTS_BATCHES)]
    #[Field(prefetchMethod: 'prefetchProductBatches', description: 'Produktové šarže')]
    public function getProductBatches(array $_): ?array
    {
        return ApiUtil::wrapItems($this->product->productBatches ?? [], ProductBatch::class);
    }

    public function prefetchProductBatches(array $products): array
    {
        $this->collection->fetchProductBatches();

        return [];
    }

    /** @return  ProductUnit[] */
    #[Module(\Modules::PRODUCTS, \Modules::SUB_UNITS)]
    #[Field(prefetchMethod: 'prefetchMeasureUnit', description: 'Měrná jednotka')]
    public function getMeasureUnit(array $_): ?array
    {
        return ApiUtil::wrapItems($this->product->measureUnits ?? [], ProductUnit::class);
    }

    public function prefetchMeasureUnit(array $products): array
    {
        $this->collection->fetchMeasureUnits();

        return [];
    }

    /**
     * @return ProductConvertor[]
     */
    #[Field(prefetchMethod: 'prefetchConvertors', description: 'Převodní tabulky.')]
    #[Module(\Modules::CONVERTORS)]
    public function getConvertors(array $_): ?array
    {
        $convertors = [];

        foreach ($this->product->convertors ?? [] as $paramValues) {
            foreach ($paramValues as $paramValue) {
                foreach ($paramValue as $value) {
                    $convertors[] = new ProductConvertor($value);
                }
            }
        }

        return $convertors;
    }

    public function prefetchConvertors(array $products): array
    {
        $this->collection->fetchConvertorsValues(true);

        return [];
    }

    /** @return ProductTemplate[] */
    #[Module(\Modules::TEMPLATES)]
    #[Field(prefetchMethod: 'prefetchProductTemplates', description: 'Šablony.')]
    public function getProductTemplates(array $_): ?array
    {
        return ApiUtil::wrapItems($this->product->templates, ProductTemplate::class);
    }

    public function prefetchProductTemplates(array $product): array
    {
        $this->collection->fetchTemplates();

        return [];
    }

    /** @return ProductAttachment[] */
    #[Field(prefetchMethod: 'prefetchProductAttachments', description: 'Přílohy produktu')]
    #[Module(\Modules::ATTACHMENTS)]
    public function getAttachments(array $_): ?array
    {
        return ApiUtil::wrapItems($this->product->attachments, ProductAttachment::class);
    }

    public function prefetchProductAttachments(array $products): array
    {
        $this->collection->fetchAttachments();

        return $products;
    }

    public function getProduct(): \Product
    {
        return $this->product;
    }

    protected function getCollection(): ProductCollection
    {
        return new ProductCollection([$this->product->id => $this->product]);
    }
}
