<?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\Installation;

use Enlight_Components_Db_Adapter_Pdo_Mysql;
use Psr\Log\LoggerInterface;
use ReflectionClass;
use Symfony\Component\DependencyInjection\Container;
use Shopware\Components\Model\ModelManager;
use Shopware\Models\Shop\Locale;
use ViisonCommon_Plugin_BootstrapV13;

class BackendSessionLocaleClassMigration
{
    /**
     * @var ModelManager
     */
    protected $entityManager;

    /**
     * @var Enlight_Components_Db_Adapter_Pdo_Mysql
     */
    protected $database;

    /**
     * @var LoggerInterface
     */
    protected $logger;

    /**
     * @var Container
     */
    protected $container;

    /**
     * @param ModelManager $entityManager
     * @param Enlight_Components_Db_Adapter_Pdo_Mysql $database
     * @param LoggerInterface $logger
     * @param Container $container
     */
    public function __construct(
        ModelManager $entityManager,
        Enlight_Components_Db_Adapter_Pdo_Mysql $database,
        LoggerInterface $logger,
        Container $container
    ) {
        $this->entityManager = $entityManager;
        $this->database = $database;
        $this->logger = $logger;
        $this->container = $container;
    }

    /**
     * Fixes all backend sessions stored in the database as well as the active session.
     */
    public function fixSessions()
    {
        $this->fixAllSessionsInDatabase();
        $this->fixActiveSession();
    }

    /**
     * Fixes all backend sessions stored in the database. The query is constructed dynamically to respect the model
     * proxy configuration. Example query for default settings:
     *
     *     UPDATE s_core_sessions_backend
     *     SET `data` = REPLACE(`data`, 's:6:"locale";O:51:"Shopware\\Proxies\\__CG__\\Shopware\\Models\\Shop\\Locale"', 's:6:"locale";O:27:"Shopware\\Models\\Shop\\Locale"')
     *     WHERE `data` LIKE '%s:6:"locale";O:51:"Shopware\\\\Proxies\\\\__CG__\\\\Shopware\\\\Models\\\\Shop\\\\Locale"%'
     */
    protected function fixAllSessionsInDatabase()
    {
        // Determine the proxy class name and resulting string as contained in the serialized sessions
        $localeProxyClassName = ViisonCommon_Plugin_BootstrapV13::getLocaleProxyClassName();
        $serializedLocalePrefix = sprintf(
            's:6:"locale";O:%1$d:"%2$s"',
            mb_strlen($localeProxyClassName),
            $localeProxyClassName
        );

        $this->logger->info('Trying to fix serialized locales in backend sessions', [
            'localeProxyClassName' => $localeProxyClassName,
            'serializedLocalePrefix' => $serializedLocalePrefix,
        ]);

        // Fix all sessions
        $queryResult = $this->database->query(
            'UPDATE s_core_sessions_backend
            SET `data` = REPLACE(`data`, :serializedLocalePrefix, :fixedSerializedLocalePrefix)
            WHERE `data` LIKE :escapedSerializedLocalePrefixMatcher',
            [
                'serializedLocalePrefix' => $serializedLocalePrefix,
                'fixedSerializedLocalePrefix' => 's:6:"locale";O:27:"Shopware\\Models\\Shop\\Locale"',
                'escapedSerializedLocalePrefixMatcher' => '%' . str_replace('\\', '\\\\', $serializedLocalePrefix) . '%',
            ]
        );

        $this->logger->info('Fixed serialized locales in backend sessions', [
            'affectedRows' => $queryResult->rowCount(),
        ]);
    }

    /**
     * Fixes the active backend session, if available.
     */
    protected function fixActiveSession()
    {
        if ($this->container->get('front')->Request() === null) {
            // Assume that without a request, we are not in a Backend session and return without touching the "auth"
            // service. This is the case for installations via CLI.
            // This additional check is strictly only necessary for SW <= 5.1, as only these versions will crash when
            // trying to access the "auth" service in such cases.
            return;
        }

        // Check that we have an identity, i.e. a session
        if (!$this->container->has('auth') || !$this->container->get('auth')->hasIdentity()) {
            return;
        }

        // Check whether the locale stored in the session needs fixing
        $authService = $this->container->get('auth');
        /** @var Locale $sessionLocale */
        $sessionLocale = $authService->getIdentity()->locale;
        $localeProxyClassName = ViisonCommon_Plugin_BootstrapV13::getLocaleProxyClassName();
        if (!($sessionLocale instanceof $localeProxyClassName)) {
            return;
        }

        // Create a new locale from the ground up and copy all values from the session's locale. We do not have to
        // register it with the entity manager, since the session's locale was been associated with it in the
        // first place.
        $newSessionLocale = new Locale();
        $newSessionLocale->setLocale($sessionLocale->getLocale());
        $newSessionLocale->setLanguage($sessionLocale->getLanguage());
        $newSessionLocale->setTerritory($sessionLocale->getTerritory());
        // Since there is no setter for the locale's ID, we need to use reflection to set it
        $reflectionObject = new ReflectionClass($newSessionLocale);
        $idReflectionProperty = $reflectionObject->getProperty('id');
        $idReflectionProperty->setAccessible(true);
        $idReflectionProperty->setValue($newSessionLocale, $sessionLocale->getId());

        // Replace the session's locale with the new, non-proxied instance
        $authService->getIdentity()->locale = $newSessionLocale;

        $this->logger->info('Replaced locale in active session with non-proxied version', [
            'sessionId' => session_id(),
            'localeProxyClassName' => $localeProxyClassName,
            'localeId' => $sessionLocale->getId(),
            'newLocaleClassName' => get_class($newSessionLocale),
        ]);
    }
}
