<?php

/*
 * This file is part of Cachet.
 *
 * (c) Alt Three Services Limited
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace CachetHQ\Cachet\Console\Commands;

use Dotenv\Dotenv;
use Dotenv\Exception\InvalidPathException;
use Illuminate\Console\Command;

/**
 * This is the install command class.
 *
 * @author James Brooks <james@alt-three.com>
 */
class InstallCommand extends Command
{
    /**
     * The console command name.
     *
     * @var string
     */
    protected $name = 'cachet:install';

    /**
     * The console command description.
     *
     * @var string
     */
    protected $description = 'Install Cachet';

    /**
     * Execute the console command.
     *
     * @return void
     */
    public function fire()
    {
        if (!$this->confirm('Do you want to install Cachet?')) {
            $this->line('Installation aborted. Goodbye!');

            return;
        }

        $this->configureEnvironmentFile();
        $this->configureKey();
        $this->configureDatabase();
        $this->configureDrivers();
        $this->configureMail();
        $this->configureCachet();

        $this->info('Cachet is installed ⚡');
    }

    /**
     * Copy the environment file.
     *
     * @return void
     */
    protected function configureEnvironmentFile()
    {
        $dir = app()->environmentPath();
        $file = app()->environmentFile();
        $path = "{$dir}/{$file}";

        if (file_exists($path)) {
            $this->line('Environment file already exists. Moving on.');

            return;
        }

        copy("$path.example", $path);
    }

    /**
     * Generate the app key.
     *
     * @return void
     */
    protected function configureKey()
    {
        $this->call('key:generate');
    }

    /**
     * Configure the database.
     *
     * @param array $default
     *
     * @return void
     */
    protected function configureDatabase(array $default = [])
    {
        $config = array_merge([
            'DB_DRIVER'   => null,
            'DB_HOST'     => null,
            'DB_DATABASE' => null,
            'DB_USERNAME' => null,
            'DB_PASSWORD' => null,
            'DB_PORT'     => null,
            'DB_PREFIX'   => null,
        ], $default);

        $config['DB_DRIVER'] = $this->choice('Which database driver do you want to use?', [
            'mysql'      => 'MySQL',
            'postgresql' => 'PostgreSQL',
            'sqlite'     => 'SQLite',
        ], $config['DB_DRIVER']);

        if ($config['DB_DRIVER'] === 'sqlite') {
            $config['DB_DATABASE'] = $this->ask('Please provide the full path to your SQLite file.', $config['DB_DATABASE']);
        } else {
            $config['DB_HOST'] = $this->ask("What is the host of your {$config['DB_DRIVER']} database?", $config['DB_HOST']);

            $config['DB_DATABASE'] = $this->ask('What is the name of the database that Cachet should use?', $config['DB_DATABASE']);

            $config['DB_USERNAME'] = $this->ask('What username should we connect with?', $config['DB_USERNAME']);

            $config['DB_PASSWORD'] = $this->secret('What password should we connect with?', $config['DB_PASSWORD']);

            if ($this->confirm('Is your database listening on a non-standard port number?')) {
                $config['DB_PORT'] = $this->anticipate('What port number is your database using?', [3306, 5432], $config['DB_PORT']);
            }
        }

        if ($this->confirm('Do you want to use a prefix on the table names?')) {
            $config['DB_PREFIX'] = $this->ask('Please enter the prefix now...', $config['DB_PREFIX']);
        }

        // Format the settings ready to display them in the table.
        $this->formatConfigsTable($config);

        if (!$this->confirm('Are these settings correct?')) {
            return $this->configureDatabase($config);
        }

        foreach ($config as $setting => $value) {
            $this->writeEnv($setting, $value);
        }
    }

    /**
     * Configure other drivers.
     *
     * @param array $default
     *
     * @return void
     */
    protected function configureDrivers(array $default = [])
    {
        $config = array_merge([
            'CACHE_DRIVER'   => null,
            'SESSION_DRIVER' => null,
            'QUEUE_DRIVER'   => null,
        ], $default);

        // Format the settings ready to display them in the table.
        $this->formatConfigsTable($config);

        $config['CACHE_DRIVER'] = $this->choice('Which cache driver do you want to use?', [
            'apc'       => 'APC(u)',
            'array'     => 'Array',
            'database'  => 'Database',
            'file'      => 'File',
            'memcached' => 'Memcached',
            'redis'     => 'Redis',
        ], $config['CACHE_DRIVER']);

        // We need to configure Redis.
        if ($config['CACHE_DRIVER'] === 'redis') {
            $this->configureRedis();
        }

        $config['SESSION_DRIVER'] = $this->choice('Which session driver do you want to use?', [
            'apc'       => 'APC(u)',
            'array'     => 'Array',
            'database'  => 'Database',
            'file'      => 'File',
            'memcached' => 'Memcached',
            'redis'     => 'Redis',
        ], $config['SESSION_DRIVER']);

        // We need to configure Redis.
        if ($config['SESSION_DRIVER'] === 'redis') {
            $this->configureRedis();
        }

        $config['QUEUE_DRIVER'] = $this->choice('Which queue driver do you want to use?', [
            'null'       => 'None',
            'sync'       => 'Synchronous',
            'database'   => 'Database',
            'beanstalkd' => 'Beanstalk',
            'sqs'        => 'Amazon SQS',
            'redis'      => 'Redis',
        ], $config['QUEUE_DRIVER']);

        // We need to configure Redis, but only if the cache driver wasn't redis.
        if ($config['QUEUE_DRIVER'] === 'redis' && ($config['SESSION_DRIVER'] !== 'redis' || $config['CACHE_DRIVER'] !== 'redis')) {
            $this->configureRedis();
        }

        // Format the settings ready to display them in the table.
        $this->formatConfigsTable($config);

        if (!$this->confirm('Are these settings correct?')) {
            return $this->configureDrivers($config);
        }

        foreach ($config as $setting => $value) {
            $this->writeEnv($setting, $value);
        }
    }

    /**
     * Configure mail.
     *
     * @param array $config
     *
     * @return void
     */
    protected function configureMail(array $config = [])
    {
        $config = array_merge([
            'MAIL_DRIVER'     => null,
            'MAIL_HOST'       => null,
            'MAIL_PORT'       => null,
            'MAIL_USERNAME'   => null,
            'MAIL_PASSWORD'   => null,
            'MAIL_ADDRESS'    => null,
            'MAIL_NAME'       => null,
            'MAIL_ENCRYPTION' => null,
        ], $config);

        // Don't continue with these settings if we're not interested in notifications.
        if (!$this->confirm('Do you want Cachet to send mail notifications?')) {
            return;
        }

        $config['MAIL_DRIVER'] = $this->choice('What driver do you want to use to send notifications?', [
            'smtp'      => 'SMTP',
            'mail'      => 'Mail',
            'sendmail'  => 'Sendmail',
            'mailgun'   => 'Mailgun',
            'mandrill'  => 'Mandrill',
            'ses'       => 'Amazon SES',
            'sparkpost' => 'SparkPost',
            'log'       => 'Log (Testing)',
        ]);

        if (!$config['MAIL_DRIVER'] === 'log') {
            if ($config['MAIL_DRIVER'] === 'smtp') {
                $config['MAIL_HOST'] = $this->ask('Please supply your mail server host');
            }

            $config['MAIL_ADDRESS'] = $this->ask('What email address should we send notifications from?');
            $config['MAIL_USERNAME'] = $this->ask('What username should we connect as?');
            $config['MAIL_PASSWORD'] = $this->secret('What password should we connect with?');
        }

        // Format the settings ready to display them in the table.
        $this->formatConfigsTable($config);

        if (!$this->confirm('Are these settings correct?')) {
            return $this->configureMail($config);
        }

        foreach ($config as $setting => $value) {
            $this->writeEnv($setting, $value);
        }
    }

    /**
     * Configure Cachet.
     *
     * @return void
     */
    protected function configureCachet()
    {
        if ($this->confirm('Do you wish to use Cachet Beacon?')) {
            $config['CACHET_BEACON'] = 'true';
        }

        if ($this->confirm('Do you wish to use Emoji? This requires a GitHub oAuth Token!')) {
            $config['GITHUB_TOKEN'] = $this->ask('Please enter your GitHub oAuth Token');
            $config['CACHET_EMOJI'] = 'true';
        }

        foreach ($config as $setting => $value) {
            $this->writeEnv($setting, $value);
        }
    }

    /**
     * Configure the redis connection.
     *
     * @return void
     */
    protected function configureRedis()
    {
        $config = [
            'REDIS_HOST'     => null,
            'REDIS_DATABASE' => null,
            'REDIS_PORT'     => null,
        ];

        $config['REDIS_HOST'] = $this->ask('What is the host of your redis server?');
        $config['REDIS_DATABASE'] = $this->ask('What is the name of the database that Cachet should use?');
        $config['REDIS_PORT'] = $this->ask('What port should Cachet use?', 6379);

        foreach ($config as $setting => $value) {
            $this->writeEnv($setting, $value);
        }
    }

    /**
     * Format the configs into a pretty table that we can easily read.
     *
     * @param array $config
     *
     * @return void
     */
    protected function formatConfigsTable(array $config)
    {
        $configRows = [];

        foreach ($config as $setting => $value) {
            $configRows[] = compact('setting', 'value');
        }

        $this->table(['Setting', 'Value'], $configRows);
    }

    /**
     * Writes to the .env file with given parameters.
     *
     * @param string $key
     * @param mixed  $value
     *
     * @return void
     */
    protected function writeEnv($key, $value)
    {
        $dir = app()->environmentPath();
        $file = app()->environmentFile();
        $path = "{$dir}/{$file}";

        try {
            (new Dotenv($dir, $file))->load();

            $envKey = strtoupper($key);
            $envValue = env($envKey) ?: 'null';

            file_put_contents($path, str_replace(
                "{$envKey}={$envValue}",
                "{$envKey}={$value}",
                file_get_contents($path)
            ));
        } catch (InvalidPathException $e) {
            throw $e;
        }
    }
}
