<?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\ViisonPickwareCommon\Classes;

use \Shopware_Components_Plugin_Bootstrap as PluginBootstrap;
use Shopware\Models\Attribute\Configuration as ArticleAttributeConfiguration;
use Shopware\Models\Menu\Menu;
use Shopware\Plugins\ViisonCommon\Classes\Installation\AclResource;
use Shopware\Plugins\ViisonCommon\Classes\Installation\AttributeColumn\AttributeColumnDescription;
use Shopware\Plugins\ViisonCommon\Classes\Installation\AttributeColumn\AttributeColumnInstaller;
use Shopware\Plugins\ViisonCommon\Classes\Installation\AttributeColumn\AttributeColumnUninstaller;
use Shopware\Plugins\ViisonCommon\Classes\Installation\AttributeConfiguration;
use Shopware\Plugins\ViisonCommon\Classes\Installation\Menu as MenuHelper;
use Shopware\Plugins\ViisonCommon\Classes\Installation\SQLHelper;

/**
 * Handles installing, updating and uninstalling ViisonPickwareCommon.
 *
 * This functionality used to be part of PickwareCommonLoader, but had to be moved into a class that is loaded with the
 * loader to ensure the newest versions of the migrations are always executed.
 *
 * @see PickwareCommonLoader
 *
 */
class PickwareCommonInstaller
{
    /**
     * @var PluginBootstrap
     */
    private $pluginBootstrap;

    /**
     * PickwareCommonInstaller constructor.
     *
     * @param PluginBootstrap $pluginBootstrap
     */
    public function __construct(PluginBootstrap $pluginBootstrap)
    {
        $this->pluginBootstrap = $pluginBootstrap;
    }

    /**
     * Perform the necessary installation steps for ViisonPickwareCommon.
     */
    public function installViisonPickwareCommon()
    {
        $sqlHelper = new SQLHelper($this->get('db'));

        // Add custom attribute columns
        // Auth: Add column to store the hashed Pickware PIN
        $sqlHelper->addColumnIfNotExists(
            's_core_auth_attributes',
            'viison_pickware_pin',
            'varchar(255) DEFAULT NULL'
        );
        // Auth: Add column to store the encoded used to hash the Pickware PIN
        $sqlHelper->addColumnIfNotExists(
            's_core_auth_attributes',
            'viison_pickware_pin_encoder',
            'varchar(255) DEFAULT NULL'
        );
        // Auth: Add column to store the hash of the fixed length app PIN (4 digits)
        $sqlHelper->addColumnIfNotExists(
            's_core_auth_attributes',
            'pickware_fixed_length_app_pin',
            'varchar(255) DEFAULT NULL'
        );
        // Rebuild attribute models
        $this->get('models')->generateAttributeModels([
            's_core_auth_attributes',
        ]);

        // Create menu item for the API log and the Pickware device management
        $createMenuItem = function (array $options) {
            $item = new Menu();
            $item->fromArray($options);

            return $item;
        };
        $menuInstallationHelper = new MenuHelper\InstallationHelper($this->get('models'), null, $createMenuItem);
        $menuInstallationHelper->ensureMenuItemInDatabaseIs([
            'parent' => $this->pluginBootstrap->Menu()->findOneBy([
                'controller' => 'ConfigurationMenu',
            ]),
            'controller' => 'ViisonPickwareCommonDeviceManagement',
            'action' => 'Index',
            'label' => 'Pickware Geräteverwaltung',
            'class' => 'sprite-media-player-phone',
            'active' => 1,
            'position' => 50,
        ]);

        // Create an ACL resource for the Pickware device management backend app
        $aclResourceHelper = new AclResource\AclResourceHelper($this->get('models'));
        $aclResourceCreator = new AclResource\AclResourceCreator($aclResourceHelper, $this->get('acl'), $this->get('models'));
        $aclResourceCreator->replaceResource('viisonpickwarecommondevicemanagement', [
            'read',
            'update',
            'delete',
        ], 'Pickware Geräteverwaltung');

        // Change attribute cofiguration field type in article detail view
        $requiredInformationField = $this->get('models')->getRepository(ArticleAttributeConfiguration::class)->findOneBy([
            'columnName' => 'viison_required_information_upon_picking',
        ]);
        if ($requiredInformationField) {
            $requiredInformationField->setColumnType('string');
            $this->get('models')->flush($requiredInformationField);
        }

        // Create or update the tables of custom models. Note: The order in which the tables are created is important,
        // because some of them create foreign key constraints to other tables!
        $this->createOrUpdateRestApiDevicesTable();

        $entityManager = $this->get('models');
        $attributeInstallationHelper = new AttributeColumnInstaller($entityManager, $sqlHelper);
        $attributeInstallationHelper->addAttributeColumnsIfNotExist([
            new AttributeColumnDescription(
                's_order_documents_attributes',
                'pickware_document_config',
                'TEXT'
            ),
        ]);

        // Add further migrations here
    }

    /**
     * Creates the 'pickware_rest_api_devices' table using the original specification and applies incremental updates as
     * necessary.
     */
    private function createOrUpdateRestApiDevicesTable()
    {
        $sqlHelper = new SQLHelper($this->get('db'));

        // Original table
        $this->get('db')->query(
            'CREATE TABLE IF NOT EXISTS `s_plugin_pickware_devices` (
                `id` int(11) NOT NULL AUTO_INCREMENT,
                `uuid` varchar(255) COLLATE utf8_unicode_ci NOT NULL,
                `name` varchar(255) COLLATE utf8_unicode_ci DEFAULT NULL,
                `appName` varchar(255) COLLATE utf8_unicode_ci DEFAULT NULL,
                `confirmed` tinyint(1) NOT NULL,
                `added` datetime NOT NULL,
                `lastLogin` datetime DEFAULT NULL,
                PRIMARY KEY (`id`),
                UNIQUE KEY `uuid_appName` (`uuid`,`appName`)
            ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;'
        );

        // v5.0.0: Rename table
        $sqlHelper->ensureTableIsRenamed('s_plugin_pickware_devices', 'pickware_rest_api_devices');
    }

    /**
     * Updates, adds new and removes obsolete PickwareCommon snippets in the database.
     */
    public function updateViisonPickwareCommonSnippets()
    {
        $this->get('shopware.snippet_database_handler')->loadToDatabase($this->getViisonPickwareCommonPath() . 'Snippets/');
    }

    /**
     * Returns the path to the ViisonPickwareCommon library contained in the plugin using the
     * loaded file of this class. That is, even if e.g. ViisonPickwarePOS includes its version
     * of 'PickwareCommonLoader.php' and ViisonPickwareMobile executes the code, the returned
     * path will point to the ViisonPickwareCommon files contained in ViisonPickwareMobile.
     *
     * This duplicates the same method in PickwareCommonLoader because it is private there and we can't guarantee that
     * a version where the visibility of that method is public is loaded at this point.
     *
     * @see PickwareCommonLoader::getViisonPickwareCommonPath()
     * @internal
     *
     * @return string
     */
    private function getViisonPickwareCommonPath()
    {
        return realpath($this->pluginBootstrap->Path() . 'ViisonPickwareCommon') . '/';
    }

    /**
     * Uninstall ViisonPickwareCommon iff at most one plugin using ViisonPickwareCommon is installed (which means this
     * method was called because the last one is being uninstalled).
     *
     * Usage: Call once in the uninstall method.
     *
     */
    public function uninstallViisonPickwareCommon()
    {
        // Only uninstall, if there is no other plugin installed, which uses PickwareCommon
        if ($this->numberOfPluginsUsingPickwareCommon() > 1) {
            return;
        }

        $database = $this->get('db');
        $sqlHelper = new SQLHelper($database);
        $modelManager = $this->get('models');

        // Remove custom attribute columns
        $sqlHelper->dropColumnIfExists(
            's_core_auth_attributes',
            'viison_pickware_pin'
        );
        $sqlHelper->dropColumnIfExists(
            's_core_auth_attributes',
            'viison_pickware_pin_encoder'
        );
        $sqlHelper->dropColumnIfExists(
            's_core_auth_attributes',
            'pickware_fixed_length_app_pin'
        );
        // Legacy columns
        $sqlHelper->dropColumnIfExists(
            's_articles_attributes',
            'viison_required_information_upon_picking'
        );
        $sqlHelper->dropColumnIfExists(
            's_order_details_attributes',
            'viison_additional_required_information'
        );

        // Rebuild attribute models
        $modelManager->generateAttributeModels([
            's_core_auth_attributes',
            // Legacy models
            's_articles_attributes',
            's_order_details_attributes',
        ]);

        // Legacy step: Remove custom attribute configurations
        $attributeConfigurationUninstallationHelper = new AttributeConfiguration\UninstallationHelper($modelManager);
        $attributeConfigurationUninstallationHelper->removeAttributeConfigurationsIfExist([
            's_articles_attributes' => [
                'viison_required_information_upon_picking',
            ],
        ]);

        // Remove menu items
        $menuItems = $modelManager->getRepository(Menu::class)->findBy([
            'controller' => [
                'ViisonPickwareCommonBarcodeLabelPrinting',
                'ViisonPickwareCommonBarcode',
                'ViisonPickwareCommonBarcodeLabelPresets',
                'ViisonPickwareCommonDeviceManagement',
            ],
        ]);
        foreach ($menuItems as $menuItem) {
            $modelManager->remove($menuItem);
        }
        $modelManager->flush($menuItems);

        // Remove custom tables
        $database->query(
            'SET FOREIGN_KEY_CHECKS = 0;
            -- Legacy tables
            DROP TABLE IF EXISTS `s_plugin_pickware_barcode_labels`;
            -- Old table names (< 5.0.0)
            DROP TABLE IF EXISTS `s_plugin_pickware_api_log`;
            DROP TABLE IF EXISTS `s_plugin_pickware_article_barcode_labels`;
            DROP TABLE IF EXISTS `s_plugin_pickware_barcode_label_preset_block`;
            DROP TABLE IF EXISTS `s_plugin_pickware_barcode_label_preset`;
            DROP TABLE IF EXISTS `s_plugin_pickware_bin_location_barcode_labels`;
            DROP TABLE IF EXISTS `s_plugin_pickware_devices`;
            DROP TABLE IF EXISTS `s_plugin_pickware_idempotency_key`;
            -- New table names (>= 5.0.0)
            DROP TABLE IF EXISTS `pickware_rest_api_devices`;
            SET FOREIGN_KEY_CHECKS = 1;'
        );

        // Remove custom ACL resources
        $aclResourceHelper = new AclResource\AclResourceHelper($modelManager);
        $aclResourceRemover = new AclResource\AclResourceRemover($aclResourceHelper, $this->get('acl'), $modelManager);
        $aclResourceRemover->deleteResource('viisonpickwarecommondevicemanagement');

        // Remove snippets
        $this->get('shopware.snippet_database_handler')->removeFromDatabase($this->getViisonPickwareCommonPath() . 'Snippets/', true);

        // Remove translations
        $database->query(
            'DELETE FROM s_core_translations
            WHERE `objecttype` = \'viison_pickware_common_barcode_label_preset_name\''
        );

        // Remove config value for the default article preset
        $this->get('viison_common.hidden_config_storage')->removeConfigValue('viisonPickwareCommonIsBarcodeLessBarcodeLabelPresetInstalled');

        $entityManager = $this->get('models');
        $attributeFieldUninstaller = new AttributeColumnUninstaller($entityManager, $sqlHelper);
        $attributeFieldUninstaller->removeAttributeColumnsIfExist([
            new AttributeColumnDescription(
                's_order_documents_attributes',
                'pickware_document_config'
            ),
        ]);
    }

    /**
     * Counts the installed plugins which use ViisonPickwareCommon.
     *
     * @return int
     */
    private function numberOfPluginsUsingPickwareCommon()
    {
        // Find all installed VIISON plugins
        $installedViisonPlugins = $this->get('db')->fetchAll(
            'SELECT source, namespace, name
            FROM s_core_plugins
            WHERE name LIKE \'Viison%\'
            AND installation_date IS NOT NULL'
        );

        // Check the plugin directories for 'ViisonPickwareCommon'
        $pluginRootPaths = Shopware()->Container()->getParameter('shopware.plugin_directories');
        $pickwareCommonPlugins = array_filter($installedViisonPlugins, function ($plugin) use ($pluginRootPaths) {
            return realpath($pluginRootPaths[$plugin['source']] . '/' . $plugin['namespace'] . '/' . $plugin['name'] . '/ViisonPickwareCommon') !== false;
        });

        return count($pickwareCommonPlugins);
    }

    /**
     * @param string $key
     * @return mixed
     */
    private function get($key)
    {
        return $this->pluginBootstrap->get($key);
    }
}
