<?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\Components\DeviceLicensing;

use Enlight_Plugin_PluginManager;
use Shopware\Bundle\PluginInstallerBundle\Service\SubscriptionService;
use Shopware\Components\Model\ModelManager;
use Shopware\CustomModels\ViisonPickwareCommon\RestApi\RestApiDevice;
use Shopware\Models\Plugin\Plugin;
use Shopware\Plugins\ViisonPickwareCommon\Classes\Util;

final class DeviceLicensingService
{
    /**
     * @var ModelManager
     */
    private $entityManager;

    /**
     * @var Enlight_Plugin_PluginManager
     */
    private $pluginManager;

    /**
     * @var SubscriptionService
     */
    private $subscriptionService;

    /**
     * @param ModelManager $entityManager
     * @param Enlight_Plugin_PluginManager $pluginManager
     * @param SubscriptionService $subscriptionService
     */
    public function __construct(
        ModelManager $entityManager,
        Enlight_Plugin_PluginManager $pluginManager,
        SubscriptionService $subscriptionService
    ) {
        $this->entityManager = $entityManager;
        $this->pluginManager = $pluginManager;
        $this->subscriptionService = $subscriptionService;
    }

    /**
     * Returns an associative array that contains information about every plugin that provides support for one or more
     * Pickware apps. That includes both the names of the supported apps and the respective device limit, among others.
     *
     * @return array
     */
    public function getAppSupportInfo()
    {
        // Collect which plugin supports which app
        $appSupportingPickwarePlugins = $this->getAppSupportingPickwarePlugins();
        $pickwareAppSupport = [];
        foreach ($appSupportingPickwarePlugins as $plugin) {
            $pickwareAppSupport[$plugin->getPlugin()->getName()] = [
                'deviceLimit' => 0,
                'isLicenseValid' => false,
                'supportedApps' => $plugin->getNamesOfSupportedPickwareApps(),
            ];
        }

        if (is_callable([$this->subscriptionService, 'getPluginInformationFromApi'])) {
            // Shopware >= 5.5, hence get the actual device limits from the Shopware store API
            try {
                $pluginInfo = $this->subscriptionService->getPluginInformationFromApi();
            } catch (\Exception $exception) {
                throw DeviceLicensingException::failedToLoadPluginInformation();
            }
            if (!$pluginInfo) {
                throw DeviceLicensingException::failedToLoadPluginInformation();
            }

            $today = new \DateTime();
            $today->setTime(0, 0, 0);
            foreach ($pluginInfo->getPlugins() as $plugin) {
                $pluginName = $plugin->getTechnicalName();
                if (!isset($pickwareAppSupport[$pluginName])) {
                    continue;
                }
                $isLicenseValid = !$plugin->isUnknownLicense() && ($plugin->getLicenseExpiration() === null || new \DateTime($plugin->getLicenseExpiration()) >= $today);
                $pickwareAppSupport[$pluginName]['isLicenseValid'] = $isLicenseValid;
                if ($isLicenseValid) {
                    // We allow to use up to 10 devices while testing the plugin
                    $pickwareAppSupport[$pluginName]['deviceLimit'] = ($plugin->getType() === 3) ? 10 : $plugin->getLicenseQuantity();
                }
            }
        } else {
            // Shopware <= 5.4, hence get the device limit and license info directly from the plugin
            foreach ($appSupportingPickwarePlugins as $plugin) {
                if (method_exists($plugin, 'getPickwareDeviceLimit')) {
                    $pickwareAppSupport[$plugin->getPlugin()->getName()]['deviceLimit'] = $plugin->getPickwareDeviceLimit();
                }
                if (method_exists($plugin, 'checkLicense')) {
                    $pickwareAppSupport[$plugin->getPlugin()->getName()]['isLicenseValid'] = $plugin->checkLicense(false);
                }
            }
        }

        return $pickwareAppSupport;
    }

    /**
     * Compares the number of registered devices with the device limit for the given app name.
     *
     * @param string $appName
     * @return boolean True, iff the number of registered devices is greater than or equal to the number of available
     *         device licenses.
     */
    public function isDeviceLimitReached($appName)
    {
        $devices = $this->entityManager->getRepository(RestApiDevice::class)->findBy([
            'appName' => $appName,
            'confirmed' => true,
        ]);

        return count($devices) >= $this->getDeviceLimit($appName);
    }

    /**
     * @param string $deviceUuid
     * @param string $appName
     * @return RestApiDevice|null
     */
    public function findPickwareDevice($deviceUuid, $appName)
    {
        return $this->entityManager->getRepository(RestApiDevice::class)->findOneBy([
            'uuid' => $deviceUuid,
            'appName' => $appName,
        ]);
    }

    /**
     * Creates and returns a new, unconfirmed device.
     *
     * @param string $deviceUuid
     * @param string $appName
     * @param string $deviceName
     * @return RestApiDevice
     */
    public function registerDevice($deviceUuid, $appName, $deviceName)
    {
        // Try to find the device with the given UUID and app name
        $device = $this->findPickwareDevice($deviceUuid, $appName);
        if ($device) {
            return $device;
        }

        return $this->createDevice($deviceUuid, $appName, $deviceName);
    }

    /**
     * @param string $deviceUuid
     * @param string $appName
     * @return boolean True, iff a device with teh given `$deviceUuid` and `$appName` exists and is confirmed.
     */
    public function isPickwareDeviceValid($deviceUuid, $appName)
    {
        $device = $this->findPickwareDevice($deviceUuid, $appName);

        return $device && $device->isConfirmed();
    }

    /**
     * Returns an array containing the plugin Bootstrap instances of all plugins that provide support for any
     * Pickware app.
     *
     * @return array
     */
    public function getAppSupportingPickwarePlugins()
    {
        $pickwarePluginNames = array_keys(Util::getPickwarePluginVersions());
        $pickwarePluginInfos = $this->entityManager->getRepository(Plugin::class)->findBy([
            'name' => $pickwarePluginNames,
            'active' => true,
        ]);
        $pickwarePlugins = array_map(
            function (Plugin $pluginInfo) {
                return $this->pluginManager->get($pluginInfo->getNamespace())->get($pluginInfo->getName());
            },
            $pickwarePluginInfos
        );
        $appSupportingPickwarePlugins = array_filter(
            $pickwarePlugins,
            function ($plugin) {
                return $plugin && method_exists($plugin, 'getNamesOfSupportedPickwareApps');
            }
        );

        return $appSupportingPickwarePlugins;
    }

    /**
     * @param string $appName
     * @return int The device limit for the Pickware app having the given `$appName`.
     */
    private function getDeviceLimit($appName)
    {
        $pickwareAppSupport = $this->getAppSupportInfo();
        foreach ($pickwareAppSupport as $appSupport) {
            if (in_array($appName, $appSupport['supportedApps'])) {
                return $appSupport['deviceLimit'];
            }
        }

        return 0;
    }

    /**
     * Creates a new device with the given UUID/app name combination.
     *
     * @param string $uuid
     * @param string $appName
     * @param string $deviceName (optional)
     * @return RestApiDevice
     */
    private function createDevice($uuid, $appName, $deviceName)
    {
        $device = new RestApiDevice();
        $device->setUUID($uuid);
        $device->setAppName($appName);
        $device->setName($deviceName);
        $this->entityManager->persist($device);
        $this->entityManager->flush($device);

        return $device;
    }
}
