<?php
/**
 * For the full copyright and license information, please view the
 * docs/licenses/LICENSE.txt file that was distributed with this source code.
 */

use PrestaShopBundle\Install\Database;
use PrestaShopBundle\Install\Install;
use PrestaShop\PrestaShop\Adapter\SymfonyContainer;
use PrestaShop\PrestaShop\Core\Context\ContextBuilderPreparer;
use Symfony\Component\Console\Output\ConsoleOutput;
use Symfony\Component\Filesystem\Filesystem;

class InstallControllerConsoleProcess extends InstallControllerConsole implements HttpConfigureInterface
{
    public $process_steps = [];
    public $previous_button = false;

    /**
     * @var Install
     */
    protected $model_install;

    /**
     * @var Database
     */
    protected $model_database;

    public function init()
    {
        $output = new ConsoleOutput();
        $logger = new SymfonyConsoleLogger($output, SymfonyConsoleLogger::INFO);

        $this->model_install = new Install(null, null, $logger);
        $this->model_install->setTranslator($this->translator);

        $this->model_database = new Database($logger);
        $this->model_database->setTranslator($this->translator);
    }

    /**
     * @see HttpConfigureInterface::processNextStep()
     */
    public function processNextStep()
    {
    }

    public function display()
    {
    }

    /**
     * @see HttpConfigureInterface::validate()
     */
    public function validate()
    {
        return false;
    }

    public function initializeContext()
    {
        global $smarty;

        // Clean all cache values
        Cache::clean('*');

        Configuration::set('PS_SHOP_DEFAULT', 1);
        Shop::initialize();
        Context::getContext()->shop = new Shop(1);
        Shop::setContext(Shop::CONTEXT_SHOP, 1);
        Configuration::loadConfiguration();
        if (!isset(Context::getContext()->language) || !Validate::isLoadedObject(Context::getContext()->language)) {
            if ($id_lang = (int) Configuration::get('PS_LANG_DEFAULT')) {
                Context::getContext()->language = new Language($id_lang);
            }
        }
        if (!isset(Context::getContext()->country) || !Validate::isLoadedObject(Context::getContext()->country)) {
            if ($id_country = (int) Configuration::get('PS_COUNTRY_DEFAULT')) {
                Context::getContext()->country = new Country((int) $id_country);
            }
        }
        if (!isset(Context::getContext()->currency) || !Validate::isLoadedObject(Context::getContext()->currency)) {
            if ($id_currency = Currency::getDefaultCurrencyId()) {
                Context::getContext()->currency = new Currency((int) $id_currency);
            }
        }

        Context::getContext()->cart = new Cart();
        Context::getContext()->employee = new Employee(1);
        if (!defined('_PS_SMARTY_FAST_LOAD_')) {
            define('_PS_SMARTY_FAST_LOAD_', true);
        }
        require_once _PS_ROOT_DIR_ . '/config/smarty.config.inc.php';

        Context::getContext()->smarty = $smarty;

        $container = SymfonyContainer::getInstance();
        /** @var ContextBuilderPreparer $preparer */
        $preparer = $container->get(ContextBuilderPreparer::class);
        $preparer->prepareFromLegacyContext(Context::getContext());
    }

    public function process()
    {
        /* avoid exceptions on re-installation */
        $this->clearConfigXML() && $this->clearConfigThemes();
        $steps = explode(',', $this->datas->step);
        if (in_array('all', $steps)) {
            $steps = ['database', 'modules', 'theme', 'fixtures', 'postInstall', 'finalize'];
        }
        if (!file_exists(PS_INSTALLATION_LOCK_FILE)) {
            // Set the install lock file
            file_put_contents(PS_INSTALLATION_LOCK_FILE, '1');
        }

        if (in_array('database', $steps)) {
            if (!$this->processGenerateSettingsFile()) {
                $this->printErrors('processGenerateSettingsFile');
            }

            if ($this->datas->database_create) {
                $this->model_database->createDatabase($this->datas->database_server, $this->datas->database_name, $this->datas->database_login, $this->datas->database_password);
            }

            if (!$this->model_database->testDatabaseSettings(
                $this->datas->database_server,
                $this->datas->database_name,
                $this->datas->database_login,
                $this->datas->database_password,
                $this->datas->database_prefix,
                $this->datas->database_clear
            )) {
                $this->printErrors('testDatabaseSettings');
            }

            // Deferred Kernel Init
            $this->initKernel();

            if (!$this->processInstallDatabase()) {
                $this->printErrors('processInstallDatabase');
            }

            if (!$this->processInstallDefaultData()) {
                $this->printErrors('processInstallDefaultData');
            }
            if (!$this->processPopulateDatabase()) {
                $this->printErrors('processPopulateDatabase');
            }

            if (!$this->processConfigureShop()) {
                $this->printErrors('processConfigureShop');
            }
        }

        // First install modules
        if (in_array('modules', $steps)) {
            if (!$this->processInstallModules()) {
                $this->printErrors('processInstallModules');
            }
        }

        // Once modules are installed enabled selected theme since it could enable/disable
        // some of the installed modules
        if (in_array('theme', $steps)) {
            if (!$this->processInstallTheme()) {
                $this->printErrors('processInstallTheme');
            }
        }

        if (in_array('fixtures', $steps) && $this->datas->fixtures) {
            if (!$this->processInstallFixtures()) {
                $this->printErrors('processInstallFixtures');
            }
        }

        if (in_array('postInstall', $steps)) {
            if (!$this->processPostInstall()) {
                $this->printErrors('processPostInstall');
            }
        }

        if (in_array('finalize', $steps)) {
            if (!$this->processFinalize()) {
                $this->printErrors('processFinalize');
            }
        }

        // Update fixtures lang
        $this->rebootWithoutTranslationsCache();
        foreach (Language::getLanguages() as $lang) {
            Language::updateMultilangTable($lang['iso_code']);
        }
    }

    /**
     * PROCESS : generateSettingsFile
     */
    public function processGenerateSettingsFile()
    {
        return $this->model_install->generateSettingsFile(
            $this->datas->database_server,
            $this->datas->database_login,
            $this->datas->database_password,
            $this->datas->database_name,
            $this->datas->database_prefix,
            $this->datas->database_engine
        );
    }

    /**
     * PROCESS : installDatabase
     * Create database structure
     */
    public function processInstallDatabase()
    {
        return $this->model_install->installDatabase($this->datas->database_clear);
    }

    /**
     * PROCESS : installDefaultData
     * Create default shop and languages
     */
    public function processInstallDefaultData()
    {
        $this->initializeContext();
        if (!$res = $this->model_install->installDefaultData($this->datas->shop_name, $this->datas->shop_country, (int) $this->datas->all_languages, true)) {
            return false;
        }

        if ($this->datas->base_uri != '/') {
            $shop_url = new ShopUrl(1);
            $shop_url->physical_uri = $this->datas->base_uri;
            $shop_url->save();
        }

        return $res;
    }

    /**
     * PROCESS : populateDatabase
     * Populate database with default data
     */
    public function processPopulateDatabase()
    {
        $this->initializeContext();

        $this->model_install->xml_loader_ids = $this->datas->xml_loader_ids;
        $result = $this->model_install->populateDatabase();
        $this->datas->xml_loader_ids = $this->model_install->xml_loader_ids;
        Configuration::updateValue('PS_INSTALL_XML_LOADERS_ID', json_encode($this->datas->xml_loader_ids));

        return $result;
    }

    /**
     * PROCESS : configureShop
     * Set default shop configuration
     */
    public function processConfigureShop()
    {
        $this->initializeContext();

        return $this->model_install->configureShop([
            'shop_name' => $this->datas->shop_name,
            'shop_country' => $this->datas->shop_country,
            'shop_timezone' => $this->datas->timezone,
            'use_smtp' => false,
            'admin_firstname' => $this->datas->admin_firstname,
            'admin_lastname' => $this->datas->admin_lastname,
            'admin_password' => $this->datas->admin_password,
            'admin_email' => $this->datas->admin_email,
            'configuration_agrement' => true,
            'enable_ssl' => $this->datas->enable_ssl,
            'rewrite_engine' => $this->datas->rewrite_engine,
        ]);
    }

    /**
     * PROCESS : installModules
     * Install all modules in ~/modules/ directory
     */
    public function processInstallModules()
    {
        $this->initializeContext();
        if (is_string($this->datas->modules)) {
            $modules = explode(',', $this->datas->modules);
        } else {
            $modules = array_keys($this->model_install->getModulesOnDisk());
        }

        return $this->model_install->installModules($modules);
    }

    /**
     * PROCESS : installFixtures
     * Install fixtures (E.g. demo products)
     */
    public function processInstallFixtures()
    {
        $this->initializeContext();

        if ((!$this->datas->xml_loader_ids || !is_array($this->datas->xml_loader_ids)) && ($xml_ids = json_decode(Configuration::get('PS_INSTALL_XML_LOADERS_ID'), true))) {
            $this->datas->xml_loader_ids = $xml_ids;
        }

        $this->model_install->xml_loader_ids = $this->datas->xml_loader_ids;
        $result = $this->model_install->installFixtures(null, ['shop_country' => $this->datas->shop_country]);
        $this->datas->xml_loader_ids = $this->model_install->xml_loader_ids;

        return $result;
    }

    /**
     * Process post install execution
     */
    public function processPostInstall(): bool
    {
        return $this->model_install->postInstall();
    }

    public function processFinalize(): bool
    {
        $this->initializeContext();

        $result = $this->model_install->finalize();

        if (!$result) {
            $this->printErrors();
        }

        return $result;
    }

    /**
     * PROCESS : installTheme
     * Install theme
     */
    public function processInstallTheme()
    {
        $this->initializeContext();

        return $this->model_install->installTheme($this->datas->theme);
    }

    private function clearConfigXML()
    {
        $configXMLPath = _PS_ROOT_DIR_ . '/config/xml/';
        $cacheFiles = scandir($configXMLPath, SCANDIR_SORT_NONE);
        $excludes = ['.htaccess', 'index.php'];

        foreach ($cacheFiles as $file) {
            $filepath = $configXMLPath . $file;
            if (is_file($filepath) && !in_array($file, $excludes)) {
                unlink($filepath);
            }
        }
    }

    private function clearConfigThemes()
    {
        $themesPath = _PS_ROOT_DIR_ . '/config/themes/';
        $cacheFiles = scandir($themesPath, SCANDIR_SORT_NONE);
        foreach ($cacheFiles as $file) {
            $file = $themesPath . $file;
            if (is_file($file)) {
                unlink($file);
            }
        }
    }

    /**
     * Deferred initialization of Symfony Kernel
     */
    private function initKernel()
    {
        require_once _PS_CORE_DIR_ . '/config/bootstrap.php';

        global $kernel;
        $kernel = new AdminKernel(_PS_ENV_, _PS_MODE_DEV_);
        $kernel->boot();
    }

    /**
     * Delete translations cache and reboot the kernel so newly installed languages are took into account
     *
     * This method is only useful in CLI as everything is done in a single call but not with the web ui
     * because the whole cache gets cleared before translating the fixtures
     */
    private function rebootWithoutTranslationsCache()
    {
        global $kernel;
        (new Filesystem())->remove($kernel->getCacheDir() . DIRECTORY_SEPARATOR . 'translations');
        $kernel->reboot($kernel->getCacheDir());
    }
}
