<?php

/**
 * @package    Grav\Console\Gpm
 *
 * @copyright  Copyright (C) 2015 - 2019 Trilby Media, LLC. All rights reserved.
 * @license    MIT License; see LICENSE file for details.
 */

namespace Grav\Console\Gpm;

use Grav\Common\Cache;
use Grav\Common\Grav;
use Grav\Common\Filesystem\Folder;
use Grav\Common\GPM\GPM;
use Grav\Common\GPM\Installer;
use Grav\Common\GPM\Response;
use Grav\Console\ConsoleCommand;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Question\ConfirmationQuestion;

class DirectInstallCommand extends ConsoleCommand
{
    /** @var string */
    protected $all_yes;

    /** @var string */
    protected $destination;

    protected function configure()
    {
        $this
            ->setName('direct-install')
            ->setAliases(['directinstall'])
            ->addArgument(
                'package-file',
                InputArgument::REQUIRED,
                'Installable package local <path> or remote <URL>. Can install specific version'
            )
            ->addOption(
                'all-yes',
                'y',
                InputOption::VALUE_NONE,
                'Assumes yes (or best approach) instead of prompting'
            )
            ->addOption(
                'destination',
                'd',
                InputOption::VALUE_OPTIONAL,
                'The destination where the package should be installed at. By default this would be where the grav instance has been launched from',
                GRAV_ROOT
            )
            ->setDescription('Installs Grav, plugin, or theme directly from a file or a URL')
            ->setHelp('The <info>direct-install</info> command installs Grav, plugin, or theme directly from a file or a URL');
    }

    /**
     * @return bool
     */
    protected function serve()
    {
        // Making sure the destination is usable
        $this->destination = realpath($this->input->getOption('destination'));

        if (
            !Installer::isGravInstance($this->destination) ||
            !Installer::isValidDestination($this->destination, [Installer::EXISTS, Installer::IS_LINK])
        ) {
            $this->output->writeln('<red>ERROR</red>: ' . Installer::lastErrorMsg());
            exit;
        }


        $this->all_yes = $this->input->getOption('all-yes');

        $package_file = $this->input->getArgument('package-file');

        $helper = $this->getHelper('question');
        $question = new ConfirmationQuestion("Are you sure you want to direct-install <cyan>{$package_file}</cyan> [y|N] ", false);

        $answer = $this->all_yes ? true : $helper->ask($this->input, $this->output, $question);

        if (!$answer) {
            $this->output->writeln('exiting...');
            $this->output->writeln('');
            exit;
        }

        $tmp_dir = Grav::instance()['locator']->findResource('tmp://', true, true);
        $tmp_zip = $tmp_dir . '/Grav-' . uniqid();

        $this->output->writeln('');
        $this->output->writeln("Preparing to install <cyan>{$package_file}</cyan>");


        if (Response::isRemote($package_file)) {
            $this->output->write('  |- Downloading package...     0%');
            try {
                $zip = GPM::downloadPackage($package_file, $tmp_zip);
            } catch (\RuntimeException $e) {
                $this->output->writeln('');
                $this->output->writeln("  `- <red>ERROR: {$e->getMessage()}</red>");
                $this->output->writeln('');
                exit;
            }

            if ($zip) {
                $this->output->write("\x0D");
                $this->output->write('  |- Downloading package...   100%');
                $this->output->writeln('');
            }
        } else {
            $this->output->write('  |- Copying package...         0%');
            $zip = GPM::copyPackage($package_file, $tmp_zip);
            if ($zip) {
                $this->output->write("\x0D");
                $this->output->write('  |- Copying package...       100%');
                $this->output->writeln('');
            }
        }

        if (file_exists($zip)) {
            $tmp_source = $tmp_dir . '/Grav-' . uniqid();

            $this->output->write('  |- Extracting package...    ');
            $extracted = Installer::unZip($zip, $tmp_source);

            if (!$extracted) {
                $this->output->write("\x0D");
                $this->output->writeln('  |- Extracting package...    <red>failed</red>');
                Folder::delete($tmp_source);
                Folder::delete($tmp_zip);
                exit;
            }

            $this->output->write("\x0D");
            $this->output->writeln('  |- Extracting package...    <green>ok</green>');


            $type = GPM::getPackageType($extracted);

            if (!$type) {
                $this->output->writeln("  '- <red>ERROR: Not a valid Grav package</red>");
                $this->output->writeln('');
                Folder::delete($tmp_source);
                Folder::delete($tmp_zip);
                exit;
            }

            $blueprint = GPM::getBlueprints($extracted);
            if ($blueprint) {
                if (isset($blueprint['dependencies'])) {
                    $dependencies = [];
                    foreach ($blueprint['dependencies'] as $dependency) {
                        if (is_array($dependency)){
                           if (isset($dependency['name'])) {
                              $dependencies[] = $dependency['name'];
                           }
                           if (isset($dependency['github'])) {
                               $dependencies[] = $dependency['github'];
                           }
                        } else {
                            $dependencies[] = $dependency;
                        }
                    }
                    $this->output->writeln('  |- Dependencies found...    <cyan>[' . implode(',', $dependencies) . ']</cyan>');

                    $question = new ConfirmationQuestion("  |  '- Dependencies will not be satisfied. Continue ? [y|N] ", false);
                    $answer = $this->all_yes ? true : $helper->ask($this->input, $this->output, $question);

                    if (!$answer) {
                        $this->output->writeln('exiting...');
                        $this->output->writeln('');
                        Folder::delete($tmp_source);
                        Folder::delete($tmp_zip);
                        exit;
                    }
                }
            }

            if ($type === 'grav') {

                $this->output->write('  |- Checking destination...  ');
                Installer::isValidDestination(GRAV_ROOT . '/system');
                if (Installer::IS_LINK === Installer::lastErrorCode()) {
                    $this->output->write("\x0D");
                    $this->output->writeln('  |- Checking destination...  <yellow>symbolic link</yellow>');
                    $this->output->writeln("  '- <red>ERROR: symlinks found...</red> <yellow>" . GRAV_ROOT . '</yellow>');
                    $this->output->writeln('');
                    Folder::delete($tmp_source);
                    Folder::delete($tmp_zip);
                    exit;
                }

                $this->output->write("\x0D");
                $this->output->writeln('  |- Checking destination...  <green>ok</green>');

                $this->output->write('  |- Installing package...  ');

                static::upgradeGrav($zip, $extracted);
            } else {
                $name = GPM::getPackageName($extracted);

                if (!$name) {
                    $this->output->writeln('<red>ERROR: Name could not be determined.</red> Please specify with --name|-n');
                    $this->output->writeln('');
                    Folder::delete($tmp_source);
                    Folder::delete($tmp_zip);
                    exit;
                }

                $install_path = GPM::getInstallPath($type, $name);
                $is_update = file_exists($install_path);

                $this->output->write('  |- Checking destination...  ');

                Installer::isValidDestination(GRAV_ROOT . DS . $install_path);
                if (Installer::lastErrorCode() == Installer::IS_LINK) {
                    $this->output->write("\x0D");
                    $this->output->writeln('  |- Checking destination...  <yellow>symbolic link</yellow>');
                    $this->output->writeln("  '- <red>ERROR: symlink found...</red>  <yellow>" . GRAV_ROOT . DS . $install_path . '</yellow>');
                    $this->output->writeln('');
                    Folder::delete($tmp_source);
                    Folder::delete($tmp_zip);
                    exit;

                }

                $this->output->write("\x0D");
                $this->output->writeln('  |- Checking destination...  <green>ok</green>');

                $this->output->write('  |- Installing package...  ');

                Installer::install(
                    $zip,
                    $this->destination,
                    $options = [
                        'install_path' => $install_path,
                        'theme' => (($type === 'theme')),
                        'is_update' => $is_update
                    ],
                    $extracted
                );
            }

            Folder::delete($tmp_source);

            $this->output->write("\x0D");

            if(Installer::lastErrorCode()) {
                $this->output->writeln("  '- <red>" . Installer::lastErrorMsg() . '</red>');
                $this->output->writeln('');
            } else {
                $this->output->writeln('  |- Installing package...    <green>ok</green>');
                $this->output->writeln("  '- <green>Success!</green>  ");
                $this->output->writeln('');
            }

        } else {
            $this->output->writeln("  '- <red>ERROR: ZIP package could not be found</red>");
        }

        Folder::delete($tmp_zip);

        // clear cache after successful upgrade
        $this->clearCache();

        return true;
    }

    private function upgradeGrav($zip, $folder, $keepFolder = false)
    {
        static $ignores = [
            'backup',
            'cache',
            'images',
            'logs',
            'tmp',
            'user',
            '.htaccess',
            'robots.txt'
        ];

        if (!is_dir($folder)) {
            Installer::setError('Invalid source folder');
        }

        try {
            $script = $folder . '/system/install.php';
            /** Install $installer */
            if ((file_exists($script) && $install = include $script) && is_callable($install)) {
                $install($zip);
            } else {
                Installer::install(
                    $zip,
                    GRAV_ROOT,
                    ['sophisticated' => true, 'overwrite' => true, 'ignore_symlinks' => true, 'ignores' => $ignores],
                    $folder,
                    $keepFolder
                );

                Cache::clearCache();
            }
        } catch (\Exception $e) {
            Installer::setError($e->getMessage());
        }
    }
}
