<?php
// Copyright (c) Pickware GmbH. All rights reserved.
// This file is part of software that is released under a proprietary license.
// You must not copy, modify, distribute, make publicly available, or execute
// its contents or parts thereof without express permission by the copyright
// holder, unless otherwise permitted by law.

namespace Shopware\Plugins\ViisonCommon\Subscribers\Api;

use Enlight_Hook_HookArgs;
use Shopware\Components\Api\Manager as ResourceManager;
use Shopware\Models\Article\Detail as ArticleDetail;
use Shopware\Plugins\ViisonCommon\Classes\Subscribers\AbstractBaseSubscriber;
use Shopware\Plugins\ViisonCommon\Classes\Util\Util;

class RestApiVariantUpdateSubscriber extends AbstractBaseSubscriber
{
    const FILTER_EVENT_NAME_VARIANT_CREATION_BEFORE = 'ViisonCommon_RestApiVariantUpdateSubscriber_VariantCreation_Before';
    const NOTIFY_EVENT_NAME_VARIANT_CREATION_AFTER = 'ViisonCommon_RestApiVariantUpdateSubscriber_VariantCreation_After';
    const FILTER_EVENT_NAME_VARIANT_UPDATE_BEFORE = 'ViisonCommon_RestApiVariantUpdateSubscriber_VariantUpdate_Before';
    const NOTIFY_EVENT_NAME_VARIANT_UPDATE_AFTER = 'ViisonCommon_RestApiVariantUpdateSubscriber_VariantUpdate_After';

    const EVENT_ARG_KEY_VARIANT = 'variant';
    const EVENT_ARG_KEY_ORIGINAL_VARIANT_POST_DATA = 'originalVariantPostData';

    /**
     * @var array
     */
    private $originalPostData = [];

    /**
     * @var int[]
     */
    private $existingVariantIds = [];

    /**
     * @inheritdoc
     */
    public static function getSubscribedEvents()
    {
        return [
            'Shopware_Controllers_Api_Articles::batchAction::before' => [
                'onBeforeArticleBatchAction',
                1000,
            ],
            'Shopware_Controllers_Api_Articles::batchAction::after' => 'onAfterArticleBatchAction',
            'Shopware_Controllers_Api_Articles::postAction::before' => [
                'onBeforeArticlePostAction',
                1000,
            ],
            'Shopware_Controllers_Api_Articles::postAction::after' => 'onAfterArticlePostAction',
            'Shopware_Controllers_Api_Articles::putAction::before' => [
                'onBeforeArticlePutAction',
                1000,
            ],
            'Shopware_Controllers_Api_Articles::putAction::after' => 'onAfterArticlePutAction',
            'Shopware_Controllers_Api_Variants::batchAction::before' => [
                'onBeforeVariantBatchAction',
                1000,
            ],
            'Shopware_Controllers_Api_Variants::batchAction::after' => 'onAfterVariantBatchAction',
            'Shopware_Controllers_Api_Variants::postAction::before' => [
                'onBeforeVariantPostAction',
                1000,
            ],
            'Shopware_Controllers_Api_Variants::postAction::after' => 'onAfterVariantPostAction',
            'Shopware_Controllers_Api_Variants::putAction::before' => [
                'onBeforeVariantPutAction',
                1000,
            ],
            'Shopware_Controllers_Api_Variants::putAction::after' => 'onAfterVariantPutAction',
        ];
    }

    /**
     * Saves the full POST data as well as the IDs of all existing variants associated with any of the articles
     * contained in the POST data in this subscriber instance. Furthermore the POST data of all articles is prepared,
     * which eventually fires a `before creation` or `before update` event for any variants contained in the POST data.
     *
     * @param Enlight_Hook_HookArgs $args
     */
    public function onBeforeArticleBatchAction(Enlight_Hook_HookArgs $args)
    {
        $request = $args->getSubject()->Request();
        $postData = $request->getPost();

        // Save original post data
        $this->originalPostData = $postData;

        $articleResource = ResourceManager::getResource('article');
        foreach ($postData as &$articleData) {
            // Determine existing variants
            $article = $this->get('models')->find(
                'Shopware\\Models\\Article\\Article',
                intval($articleResource->getIdByData($articleData))
            );
            if ($article) {
                foreach ($article->getDetails() as $variant) {
                    $this->existingVariantIds[] = $variant->getId();
                }
            }

            // Prepare the article data
            $articleData = $this->prepareArticlePostData($articleData);
        }
        unset($articleData);
        Util::setRequestPostData($postData);
    }

    /**
     * Processes the original POST data as saved in the before hook to apply any custom logic on all variants created or
     * updated by the request by firing an `after creation` or `after update` event, respectively.
     *
     * @param \Enlight_Event_EventArgs $args
     */
    public function onAfterArticleBatchAction(\Enlight_Event_EventArgs $args)
    {
        $view = $args->getSubject()->View();
        if (!$view->success) {
            return;
        }

        foreach ($this->originalPostData as $articleData) {
            $this->applyArticlePostData($articleData);
        }
    }

    /**
     * Saves the full POST data in this subscriber instance and prepared the article POST data, which eventually fires a
     * `before creation` event for any variants contained in the POST data.
     *
     * @param Enlight_Hook_HookArgs $args
     */
    public function onBeforeArticlePostAction(Enlight_Hook_HookArgs $args)
    {
        $request = $args->getSubject()->Request();
        $postData = $request->getPost();

        // Save original post data
        $this->originalPostData = $postData;

        // Prepare the article data
        $postData = $this->prepareArticlePostData($postData);
        Util::setRequestPostData([]);
        Util::setRequestPostData($postData);
    }

    /**
     * Processes the original POST data as saved in the before hook to apply any custom logic on all variants created or
     * updated by the request by firing an `after creation` event or `after update` event,  respectively.
     *
     * @param Enlight_Hook_HookArgs $args
     */
    public function onAfterArticlePostAction(Enlight_Hook_HookArgs $args)
    {
        $view = $args->getSubject()->View();
        if (!$view->success) {
            return;
        }

        $this->applyArticlePostData($this->originalPostData);
    }

    /**
     * Saves the full POST data as well as the IDs of all existing variants contained in the POST data in this
     * subscriber instance. Furthermore the article POST data is prepared, which eventually fires a `before creation` or
     * `before update` event for any variants contained in the POST data.
     *
     * @param Enlight_Hook_HookArgs $args
     */
    public function onBeforeArticlePutAction(Enlight_Hook_HookArgs $args)
    {
        $request = $args->getSubject()->Request();
        $postData = $request->getPost();

        $useNumberAsId = boolval($request->getParam('useNumberAsId', 0));
        if ($useNumberAsId) {
            $postData['mainDetail']['number'] = $request->getParam('id');
        }

        $articleResource = ResourceManager::getResource('article');
        $articleId = $articleResource->getIdByData($postData);
        if (!$articleId) {
            // Patch the post data to add an id, since it is not set but needed when applying the data later. We do this
            // before saving the post data in the request, because the original post data is applied after the main
            // action was executed.
            $articleId = $request->getParam('id');
            $postData['id'] = $articleId;
        }

        $article = $this->get('models')->find('Shopware\\Models\\Article\\Article', $articleId);
        if ($article && isset($postData['mainDetail']) && !isset($postData['mainDetail']['id']) && !isset($postData['mainDetail']['number']) && $article->getMainDetail()) {
            // Patch the post data to add an identifier for the 'mainDetail', since it is not set but needed when
            // applying the data later. We do this before saving the post data in the request, because the original post
            // data is applied after the main action was executed.
            $postData['mainDetail']['id'] = $article->getMainDetail()->getId();
        }

        // Save original post data
        $this->originalPostData = $postData;

        // Determine existing variants
        if ($article) {
            $this->existingVariantIds = $article->getDetails()->map(function (ArticleDetail $variant) {
                return $variant->getId();
            })->toArray();
        }

        // Prepare the article data
        $postData = $this->prepareArticlePostData($postData);
        Util::setRequestPostData([]);
        Util::setRequestPostData($postData);
    }

    /**
     * Processes the original POST data as saved in the before hook to apply any custom logic on all variants created or
     * updated by the request by firing an `after creation` or `after update` event, respectively.
     *
     * @param Enlight_Hook_HookArgs $args
     */
    public function onAfterArticlePutAction(Enlight_Hook_HookArgs $args)
    {
        $view = $args->getSubject()->View();
        if (!$view->success) {
            return;
        }

        $this->applyArticlePostData($this->originalPostData);
    }

    /**
     * Saves the full POST data as well as the IDs of all existing variants contained in the POST data in this
     * subscriber instance. Furthermore the variant POST data is prepared, which eventually fires a `before creation` or
     * `before update` event for any variants contained in the POST data.
     *
     * @param Enlight_Hook_HookArgs $args
     */
    public function onBeforeVariantBatchAction(Enlight_Hook_HookArgs $args)
    {
        $request = $args->getSubject()->Request();
        $postData = $request->getPost();

        // Save original post data
        $this->originalPostData = $postData;

        $variantResource = ResourceManager::getResource('variant');
        foreach ($postData as &$variantData) {
            // Determine existing variants
            $variant = $this->get('models')->find(
                'Shopware\\Models\\Article\\Detail',
                intval($variantResource->getIdByData($variantData))
            );
            if ($variant) {
                $this->existingVariantIds[] = $variant->getId();
            }

            // Prepare the variant data
            $variantData = $this->prepareVariantPostData($variantData);
        }
        unset($variantData);
        Util::setRequestPostData($postData);
    }

    /**
     * Processes the original POST data as saved in the before hook to apply any custom logic on all variants created or
     * updated by the request by firing an `after creation` event or `after update` event, respectively.
     *
     * @param \Enlight_Event_EventArgs $args
     */
    public function onAfterVariantBatchAction(\Enlight_Event_EventArgs $args)
    {
        $view = $args->getSubject()->View();
        if (!$view->success) {
            return;
        }

        foreach ($this->originalPostData as $variantData) {
            $this->applyVariantPostData($variantData);
        }
    }

    /**
     * Saves the full POST data in this subscriber instance and prepares the variant POST data, which eventually fires a
     * `before creation` event for any variants contained in the POST data.
     *
     * @param Enlight_Hook_HookArgs $args
     */
    public function onBeforeVariantPostAction(Enlight_Hook_HookArgs $args)
    {
        $request = $args->getSubject()->Request();
        $postData = $request->getPost();

        // Save original post data
        $this->originalPostData = $postData;

        // Prepare the variant data
        $postData = $this->prepareVariantPostData($postData);
        Util::setRequestPostData([]);
        Util::setRequestPostData($postData);
    }

    /**
     * Processes the original POST data as saved in the before hook to apply any custom logic on the variant created by
     * the request by firing an `after creation` event.
     *
     * @param Enlight_Hook_HookArgs $args
     */
    public function onAfterVariantPostAction(Enlight_Hook_HookArgs $args)
    {
        $view = $args->getSubject()->View();
        if (!$view->success) {
            return;
        }

        $this->applyVariantPostData($this->originalPostData);
    }

    /**
     * Saves the full POST data as well as the ID of the variant that is about to be updated in this subscriber
     * instance. Furthermore the variant POST data is prepared, which eventually fires a `before update` event for the
     * variant contained in the POST data.
     *
     * @param Enlight_Hook_HookArgs $args
     */
    public function onBeforeVariantPutAction(Enlight_Hook_HookArgs $args)
    {
        $request = $args->getSubject()->Request();
        $postData = $request->getPost();

        $useNumberAsId = boolval($request->getParam('useNumberAsId', 0));
        if ($useNumberAsId) {
            $postData['number'] = $request->getParam('id');
        }

        $variantResource = ResourceManager::getResource('variant');
        $variantId = $variantResource->getIdByData($postData);
        if (!$variantId) {
            // Patch the post data to add an id, since it is not set but needed when applying the data later. We do this
            // before saving the post data in the request, because the original post data is applied after the main
            // action was executed.
            $variantId = intval($request->getParam('id'));
            $postData['id'] = $variantId;
        }

        // Save original post data
        $this->originalPostData = $postData;

        // Determine existing variants
        $this->existingVariantIds = [$variantId];

        // Prepare the variant data
        $postData = $this->prepareVariantPostData($postData);
        Util::setRequestPostData([]);
        Util::setRequestPostData($postData);
    }

    /**
     * Processes the original POST data as saved in the before hook to apply any custom logic on the variant updated by
     * the request by firing an `after update` event.
     *
     * @param Enlight_Hook_HookArgs $args
     */
    public function onAfterVariantPutAction(Enlight_Hook_HookArgs $args)
    {
        $view = $args->getSubject()->View();
        if (!$view->success) {
            return;
        }

        $this->applyVariantPostData($this->originalPostData);
    }

    /**
     * @param array $articleData
     * @return array
     */
    protected function prepareArticlePostData(array $articleData)
    {
        if (isset($articleData['mainDetail'])) {
            $articleData['mainDetail'] = $this->prepareVariantPostData($articleData['mainDetail']);
        }
        if (isset($articleData['variants']) && is_array($articleData['variants'])) {
            foreach ($articleData['variants'] as &$variantData) {
                $variantData = $this->prepareVariantPostData($variantData);
            }
            unset($variantData);
        }

        return $articleData;
    }

    /**
     * @param array $variantData
     * @return array
     */
    protected function prepareVariantPostData(array $variantData)
    {
        $variantResource = ResourceManager::getResource('variant');
        $variantId = $variantResource->getIdByData($variantData);

        // Collect the event parameters based on whether the variant will be updated or created
        $eventName = null;
        $eventArgs = [];
        if (in_array($variantId, $this->existingVariantIds)) {
            $eventName = self::FILTER_EVENT_NAME_VARIANT_UPDATE_BEFORE;
            $eventArgs[self::EVENT_ARG_KEY_VARIANT] = $this->get('models')->find(
                'Shopware\\Models\\Article\\Detail',
                $variantId
            );
        } else {
            $eventName = self::FILTER_EVENT_NAME_VARIANT_CREATION_BEFORE;
        }

        return $this->get('events')->filter($eventName, $variantData, $eventArgs);
    }

    /**
     * @param array $articleData
     */
    protected function applyArticlePostData(array $articleData)
    {
        // Try to find the article using the respective resource
        $articleResource = ResourceManager::getResource('article');
        $article = $this->get('models')->find(
            'Shopware\\Models\\Article\\Article',
            intval($articleResource->getIdByData($articleData))
        );
        if (!$article) {
            return;
        }

        $this->get('models')->refresh($article);

        if (isset($articleData['mainDetail'])) {
            $this->applyVariantPostData($articleData['mainDetail']);
        }
        if (isset($articleData['variants']) && is_array($articleData['variants'])) {
            foreach ($articleData['variants'] as $variantData) {
                $this->applyVariantPostData($variantData);
            }
        }
    }

    /**
     * @param array $variantData
     */
    protected function applyVariantPostData(array $variantData)
    {
        // Try to find the variant using the respective resource
        $variantResource = ResourceManager::getResource('variant');
        $variant = $this->get('models')->find(
            'Shopware\\Models\\Article\\Detail',
            intval($variantResource->getIdByData($variantData))
        );
        if (!$variant) {
            return;
        }

        // Collect the event parameters based on whether the variant was updated or created
        $eventName = null;
        $eventArgs = [
            self::EVENT_ARG_KEY_VARIANT => $variant,
            self::EVENT_ARG_KEY_ORIGINAL_VARIANT_POST_DATA => $variantData,
        ];
        if (in_array($variant->getId(), $this->existingVariantIds)) {
            $eventName = self::NOTIFY_EVENT_NAME_VARIANT_UPDATE_AFTER;
        } else {
            $eventName = self::NOTIFY_EVENT_NAME_VARIANT_CREATION_AFTER;
        }

        $this->get('events')->notify($eventName, $eventArgs);
    }
}
