<?php namespace System\Console;

use InvalidArgumentException;
use Str;
use System\Console\BaseScaffoldCommand;
use System\Classes\VersionManager;
use Yaml;

/**
 * Scaffolds a new migration file
 *
 * @TODO:
 * - Add flag to either create a new version automatically in version.yaml or
 *   add the migration to a specific version, would also put the migration in
 *   a version specific folder
 */
class CreateMigration extends BaseScaffoldCommand
{
    /**
     * @var string|null The default command name for lazy loading.
     */
    protected static $defaultName = 'create:migration';

    /**
     * @var string The name and signature of this command.
     */
    protected $signature = 'create:migration
        {plugin : The name of the plugin. <info>(eg: Winter.Blog)</info>}
        {--name= : The name of the migration}
        {--model= : The model to create a migration for. <info>(eg: Post)</info>}
        {--table= : The table to migrate, defaults to autogenerated from the provided model. <info>(eg: winter_blog_posts)</info>}
        {--for-version= : Generate a migration for a specific version}
        {--c|create : Generate a migration that creates the specified table}
        {--u|update : Generate a migration that updates the specified table}
        {--f|force : Overwrite existing files with generated files.}
        {--uninspiring : Disable inspirational quotes}
    ';

    /**
     * @var string The console command description.
     */
    protected $description = 'Creates a new migration.';

    /**
     * @var array List of commands that this command replaces (aliases)
     */
    protected $replaces = [
        'make:migration',
    ];

    /**
     * @var string The type of class being generated.
     */
    protected $type = 'Migration';

    /**
     * @var bool Validate the provided plugin input against the PluginManager.
     */
    protected $validatePluginInput = true;

    /**
     * @var array Available migration scaffolds and their types
     */
    protected $migrationScaffolds = [
        'create' => [
            'scaffold/migration/migration.create.stub' => 'updates/{{version}}/{{snake_name}}.php',
        ],
        'update' => [
            'scaffold/migration/migration.update.stub' => 'updates/{{version}}/{{snake_name}}.php',
        ],
        'migrate' => [
            'scaffold/migration/migration.stub' => 'updates/{{version}}/{{snake_name}}.php',
        ],
    ];

    /**
     * Make all stubs.
     */
    public function makeStubs(): void
    {
        parent::makeStubs();

        $plugin = $this->getPlugin();

        // Identify the changes to insert into the version.yaml file
        $changes = [$this->vars['name']];
        $stubs = array_keys($this->stubs);
        foreach ($stubs as $stub) {
            $changes[] = Str::after($this->getDestinationForStub($stub), $plugin->getPluginPath() . '/updates/');
        }

        // Identify the version to insert these changes into
        $versions = $plugin->getPluginVersions();
        $firstVersion = array_keys($versions)[0];
        $usesV = Str::startsWith($firstVersion, 'v');
        $version = $usesV ? $this->vars['version'] : Str::after($this->vars['version'], 'v');

        // Insert these changes into the identified version
        $changes = array_merge($versions[$version] ?? [], $changes);
        $versions[$version] = $changes;

        // Render and save the updated version.yaml file
        $destinationFile = $plugin->getPluginPath() . '/updates/version.yaml';
        $this->files->put($destinationFile, Yaml::render($versions));
        $this->comment('File updated: ' . str_replace(base_path(), '', $destinationFile));
    }

    /**
     * Get the desired class name from the input.
     */
    protected function getNameInput(): string
    {
        $name = trim($this->option($this->nameFrom));
        if (empty($name)) {
            if ($this->option('create')) {
                $template = 'Create {ResourceName} Table';
            } elseif ($this->option('update')) {
                $template = 'Update {ResourceName} Table';
            } else {
                $template = now()->format('Y_m_d_His');
            }

            $resourceName = (
                $this->option('model')
                    ? Str::plural($this->option('model'))
                    : null
                )
                ?? (
                    $this->option('table')
                        ? Str::replace('_', ' ', $this->option('table'))
                        : ''
                );
            $name = Str::replace('{ResourceName}', $resourceName, $template);
        }
        return $name;
    }

    /**
     * Prepare variables for stubs.
     */
    protected function prepareVars(): array
    {
        $parts = explode('.', $this->getPluginIdentifier());
        $plugin = array_pop($parts);
        $author = array_pop($parts);
        $name = $this->getNameInput();
        $table = $this->option('table');
        $model = $this->option('model');

        if (empty($table) && !empty($model)) {
            $modelClass = "\\{$author}\\{$plugin}\Models\\{$model}";
            if (class_exists($modelClass)) {
                $table = (new $modelClass)->getTable();
            } else {
                throw new InvalidArgumentException("The model [{$modelClass}] does not exist.");
            }
        }

        if ($this->option('create') && $this->option('update')) {
            $this->error('The create & update options cannot both be set at the same time');
            return false;
        }

        if ($this->option('create')) {
            $scaffold = 'create';
        } elseif ($this->option('update')) {
            $scaffold = 'update';
        } else {
            $scaffold = 'migrate';
        }

        if (in_array($scaffold, ['create', 'update']) && empty($table)) {
            throw new InvalidArgumentException('The table or model options are required when using the create or update options');
        }

        $this->stubs = $this->migrationScaffolds[$scaffold];

        if (!empty($this->option('for-version'))) {
            $version = $this->option('for-version');
        } else {
            $currentVersion = $this->getPlugin()->getPluginVersion();
            if ($currentVersion === VersionManager::NO_VERSION_VALUE) {
                throw new InvalidArgumentException('The plugin [' . $this->getPluginIdentifier() . '] does not have a version set and no --version option was provided. Please set a version in the plugin\'s updates/version.yaml file.');
            }
            $version = $this->getNextVersion($currentVersion);
        }

        $vars = [
            'name' => $name,
            'author' => $author,
            'plugin' => $plugin,
            'version' => $version,
        ];

        if (!empty($table)) {
            $vars['table'] = $table;
        }

        return $vars;
    }

    /**
     * Get the next version number based on the current number.
     */
    protected function getNextVersion($currentVersion): string
    {
        $currentVersion = ltrim($currentVersion, 'v');
        $parts = explode('.', $currentVersion);
        $parts[count($parts) - 1] = (int) $parts[count($parts) - 1] + 1;
        return 'v' . implode('.', $parts);
    }
}
