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

use \Shopware_Components_Plugin_Bootstrap;
use Shopware\Plugins\ViisonCommon\Components\ExceptionTranslation\LocalizableThrowable;
use Shopware\Plugins\ViisonCommon\Components\ExceptionTranslation\MessageLocalization;

/**
 * Exception to be used for all errors that can occur during installation and updating of a plugin.
 */
class InstallationException extends \Exception implements LocalizableThrowable
{
    use MessageLocalization;

    /**
     * @param $message
     * @param $snippetName
     * @param mixed[] $snippetArguments
     */
    public function __construct($message, $snippetName, array $snippetArguments = [])
    {
        parent::__construct($message);

        $this->setSnippetName($snippetName);
        $this->setSnippetNamespace('exceptions/viison_common/installation_exception');
        $this->setSnippetArguments($snippetArguments);
    }

    /**
     * Returns an exception to be thrown when a plugin is going to be uninstalled or disabled but there is still a
     * plugin active/installed that requires this plugin.
     *
     * @param Shopware_Components_Plugin_Bootstrap $pluginBootstrap Bootstrap of the plugin that should be uninstalled
     * or disabled.
     * @param string $nameOfDependentPlugin Name of the plugin that still requires the plugin that should be removed or
     * uninstalled.
     * @return InstallationException
     */
    public static function dependentPluginExists(Shopware_Components_Plugin_Bootstrap $pluginBootstrap, $nameOfDependentPlugin)
    {
        return new self(
            sprintf(
                'Plugin "%s" cannot be disabled or uninstalled, because plugin "%s" depends on it and has to be ' .
                'disabled or uninstalled first.',
                $pluginBootstrap->getLabel(),
                $nameOfDependentPlugin
            ),
            'dependentPluginExists',
            [
                $pluginBootstrap->getLabel(),
                $nameOfDependentPlugin,
            ]
        );
    }

    /**
     * Returns an exception to be thrown when an installation is blocked due to an active installation lock of another
     * plugin.
     *
     * @param Shopware_Components_Plugin_Bootstrap $bootstrapOfPluginAcquiringTheLock Bootstrap of the plugin that
     * should be installed.
     * @param string $labelOfPluginHoldingTheLock Label (display name) of the plugin that currently holds the lock.
     * @param int $remainingLockTimeInSeconds Remaining time of the lock in seconds.
     * @return InstallationException
     */
    public static function installationBlockedByActiveLock(
        Shopware_Components_Plugin_Bootstrap $bootstrapOfPluginAcquiringTheLock,
        $labelOfPluginHoldingTheLock,
        $remainingLockTimeInSeconds
    ) {
        $remainingLockTimeInMinutes = ceil($remainingLockTimeInSeconds / 60);

        return new self(
            sprintf(
                'The installation or update of Plugin "%s" is blocked because an update of the plugin "%s" is ' .
                'currently running. Please try again in %d minutes at the earliest or when the update of the named ' .
                'plugin has been finished.',
                $bootstrapOfPluginAcquiringTheLock->getLabel(),
                $labelOfPluginHoldingTheLock,
                $remainingLockTimeInMinutes
            ),
            'installationLock',
            [
                $bootstrapOfPluginAcquiringTheLock->getLabel(),
                $labelOfPluginHoldingTheLock,
                $remainingLockTimeInMinutes,
            ]
        );
    }

    /**
     * Returns an exception to be thrown when a plugin cannot be installed/updated/activated because a required PHP
     * extension is missing.
     *
     * @param Shopware_Components_Plugin_Bootstrap $pluginBootstrap Bootstrap of the plugin to be
     * updated/installed/activated
     * @param string $requiredExtensionName Name of the required PHP extension
     * @return InstallationException
     */
    public static function requiredPhpExtensionMissing(Shopware_Components_Plugin_Bootstrap $pluginBootstrap, $requiredExtensionName)
    {
        return new self(
            sprintf(
                'Cannot install, update or activate plugin "%s" because it requires the missing PHP extension "%s". ' .
                'Please install and activate the required PHP extension and try again.',
                $pluginBootstrap->getLabel(),
                $requiredExtensionName
            ),
            'requiredPhpExtensionMissing',
            [
                $pluginBootstrap->getLabel(),
                $requiredExtensionName,
            ]
        );
    }

    /**
     * Returns an exception to be thrown when a plugin cannot be installed/updated/activated because a required plugin
     * is missing.
     *
     * @param Shopware_Components_Plugin_Bootstrap $pluginBootstrap Bootstrap of the plugin to install/update/enable
     * @param string $requiredPluginName Name of the required plugin
     * @param string $requiredMinimumPluginVersion Least version of the required plugin
     * @param string|null $exclusiveMaxPluginVersion
     * @return InstallationException
     */
    public static function requiredPluginMissing(
        Shopware_Components_Plugin_Bootstrap $pluginBootstrap,
        $requiredPluginName,
        $requiredMinimumPluginVersion,
        $exclusiveMaxPluginVersion = null
    ) {
        $pluginVersionConstraints = '>= ' . $requiredMinimumPluginVersion;
        if ($exclusiveMaxPluginVersion !== null) {
            $pluginVersionConstraints .= ', < ' . $exclusiveMaxPluginVersion;
        }

        $message = 'Cannot install, update or activate plugin "%s" because it requires plugin "%s" (version %s) to be installed and active. Please install and activate the plugin in the required version and try again.';
        $snippetArguments = [
            $pluginBootstrap->getLabel(),
            $requiredPluginName,
            $pluginVersionConstraints,
        ];

        return new self(vsprintf($message, $snippetArguments), 'requiredPluginMissing', $snippetArguments);
    }

    /**
     * Returns an exception to be thrown when a plugin does not support the update from a specific version and needs an
     * intermediate update step to another version first.
     *
     * @param Shopware_Components_Plugin_Bootstrap $pluginBootstrap Bootstrap of plugin that is being updated
     * @param string $oldVersion The current installed version to update from
     * @param string $newVersion The version to update to
     * @param string|null $minSupportedUpdateVersion The minimum version that is required to perform an update, or null
     *                                               if no minimum version is required
     * @return InstallationException
     */
    public static function updateFromVersionNotSupported(
        Shopware_Components_Plugin_Bootstrap $pluginBootstrap,
        $oldVersion,
        $newVersion,
        $minSupportedUpdateVersion = null
    ) {
        $message = 'The plugin "%s" does not support updates from version %s to version %s.';
        if ($minSupportedUpdateVersion) {
            $message .= ' Please update the plugin to version %s first and then try to update it again.';
        }
        $snippetName = $minSupportedUpdateVersion ? 'updateFromVersionNotSupported/updateToMinimumVersionFirst' : 'updateFromVersionNotSupported/noMinimumVersion';

        return new self(
            sprintf(
                $message,
                $pluginBootstrap->getLabel(),
                $oldVersion,
                $newVersion,
                $minSupportedUpdateVersion
            ),
            $snippetName,
            [
                $pluginBootstrap->getLabel(),
                $oldVersion,
                $newVersion,
                $minSupportedUpdateVersion,
            ]
        );
    }

    /**
     * Returns an exception to be thrown when a plugin detected an invalid re-install. That is a safe uninstall in a
     * version, from which an update is no longer supported, followed by an install. Insted an intermediate update step
     * to another version is required first.
     *
     * @param Shopware_Components_Plugin_Bootstrap $pluginBootstrap Bootstrap of plugin that is being installed.
     * @param string $previouslyInstalledVersion The version installed priort to the safe uninstall.
     * @param string $newVersion The plugin version that should be installed.
     * @param string $minSupportedUpdateVersion The minimum version that is required to perform an installation/update.
     * @return InstallationException
     */
    public static function invalidReInstall(
        Shopware_Components_Plugin_Bootstrap $pluginBootstrap,
        $previouslyInstalledVersion,
        $newVersion,
        $minSupportedUpdateVersion
    ) {
        $snippetArguments = [
            $pluginBootstrap->getLabel(),
            $previouslyInstalledVersion,
            $newVersion,
            $minSupportedUpdateVersion,
        ];

        return new self(
            vsprintf(
                'The plugin "%1$s" detected an invalid re-install attempt. When the plugin was uninstalled without deleting any data, its version was "%2$s", which is not supported for an incremental installation of version "%3$s". Please install the plugin in version "%4$s" first and then try to update it to the latest version.',
                $snippetArguments
            ),
            'invalidReInstall',
            $snippetArguments
        );
    }

    /**
     * Returns an exception that can be thrown when an outdated byte code cache (OPcache, APC, APCu cache etc.) is
     * detected.
     *
     * @param Shopware_Components_Plugin_Bootstrap $pluginBootstrap Bootstrap of the plugin, whose operation failed.
     * @return InstallationException
     */
    public static function byteCodeCacheOutdated(Shopware_Components_Plugin_Bootstrap $pluginBootstrap)
    {
        return new self(
            sprintf(
                'The requested task could not be performed for plugin "%s", because an older version of its files ' .
                'was cached. The cache was just cleared, so please try again.',
                $pluginBootstrap->getLabel()
            ),
            'byteCodeCacheOutdated',
            [
                $pluginBootstrap->getLabel(),
            ]
        );
    }

    /**
     * Returns an exception that can be thrown when the installed Shopware version does not meet the minimum required
     * version constraint of the plugin.
     *
     * @param Shopware_Components_Plugin_Bootstrap $pluginBootstrap Bootstrap of the plugin, whose operation failed.
     * @param string $minRequiredShopwareVersion
     * @param string $installedShopwareVersion
     * @return InstallationException
     */
    public static function shopwareVersionNotSufficient(
        Shopware_Components_Plugin_Bootstrap $pluginBootstrap,
        $minRequiredShopwareVersion,
        $installedShopwareVersion
    ) {
        return new self(
            sprintf(
                'Cannot install, update or activate plugin "%s", because it requires Shopware version %s or later. ' .
                'Your Shopware version is %s.',
                $pluginBootstrap->getLabel(),
                $minRequiredShopwareVersion,
                $installedShopwareVersion
            ),
            'shopwareVersionNotSufficient',
            [
                $pluginBootstrap->getLabel(),
                $minRequiredShopwareVersion,
                $installedShopwareVersion,
            ]
        );
    }
}
