<?php

namespace Ignite\Core\Console\Data;

use Illuminate\Console\Command;
use Monolog\Handler\StreamHandler;
use Monolog\Logger;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Input\InputArgument;

class Encrypt extends Command
{
    /**
     * The console command name.
     *
     * @var string
     */
    protected $name = 'ignite:data:encrypt';

    /**
     * The console command description.
     *
     * @var string
     */
    protected $description = 'Encrypt column data in a table.';

    /**
     * The log file to keep track of the changes.
     *
     * @var Logger|null
     */
    protected $log;

    /**
     * Execute the console command.
     *
     * @return mixed
     * @throws \Exception
     */
    public function handle()
    {
        $table = $this->input->getArgument('table');
        $columns = $this->input->getArgument('columns');
        $primaryKey = $this->input->getArgument('key');
        $skipQuestions = $this->option('skip-questions') || false;
        $fields = implode(',', $columns);
        $rows = app('db')->table($table)->count();

        if ($skipQuestions || $this->confirm("There are <fg=yellow>$rows</> rows in the <fg=yellow>$table</> table. Are you sure you want encrypt all data for these columns: <fg=yellow>$fields</>")) {
            $this->output->writeln('<fg=yellow>Encrypting...</>');
            $this->log()->info("Starting encryption process with table `$table`.");

            try {
                $this->encrypt($table, $primaryKey, $columns);
            } catch (\Exception $e) {
                $this->output->error($e->getMessage());
                return 1;
            }

            $this->log()->info("Finished encryption process with table `$table`.");
            $this->output->writeln("<fg=green>✓</> Done - See log file at {$this->logPath()}");

            return 0;
        }

        $this->output->writeln('<fg=yellow>Not encrypting</>');
        $this->output->writeln('<fg=green>✓</> Done');

        return 0;
    }

    /**
     * Encrypt the data for the given columns in the given table.
     *
     * @param  string $table
     * @param  string $primaryKey
     * @param  array $columns
     * @throws \Exception
     */
    protected function encrypt(string $table, string $primaryKey, array $columns)
    {
        $rows = app('db')->table($table)->get(array_merge([$primaryKey], $columns));

        $this->output->progressStart(count($rows));

        foreach ($rows as $number => $row) {
            foreach ($columns as $column) {
                $this->encryptColumn($table, $row, $column, $primaryKey);
            }
            $this->output->progressAdvance();
        }

        $this->output->progressFinish();
    }

    /**
     * Encrypt a single column.
     *
     * @param  string $table
     * @param  \stdClass $row
     * @param  string $column
     * @param  mixed $primaryKey
     * @return bool
     * @throws \Exception
     */
    protected function encryptColumn($table, $row, $column, $primaryKey)
    {
        $value = $row->$column;
        $key = $row->$primaryKey;

        if (empty($value)) {
            $this->log()->info("Row $key - $column value is empty.");
            return false;
        }

        try {
            decrypt($value);
            $this->log()->info("Row $key - $column value is already encrypted.");
        } catch (\Illuminate\Contracts\Encryption\DecryptException $e) {
            app('db')->table($table)
                ->where($primaryKey, '=', $key)
                ->update([$column => encrypt($value)]);
            $this->log()->notice("Row $key - $column encrypted.");
        }

        return true;
    }

    /**
     * Get the console command arguments.
     *
     * @return array
     */
    protected function getArguments()
    {
        return [
            ['table', InputArgument::REQUIRED, "'table' The name of the table containing columns to encrypt."],
            ['key', InputArgument::REQUIRED, "'key' The primary key column of the table."],
            ['columns', InputArgument::IS_ARRAY | InputArgument::REQUIRED, "'columns' The columns to encrypt."],
        ];
    }

    /**
     * Get the console command options.
     *
     * @return array
     */
    protected function getOptions()
    {
        return [
            ['skip-questions', 's', InputOption::VALUE_NONE, 'Skip questions'],
        ];
    }

    /**
     * Get the logger.
     *
     * @return Logger|null
     * @throws \Exception
     */
    protected function log()
    {
        if (! is_null($this->log)) {
            return $this->log;
        }

        $path = $this->logPath();

        if (file_exists($path)) {
            unlink($path);
        }

        $this->log = new Logger('data.encrypt');
        $this->log->pushHandler(
            new StreamHandler($path, Logger::DEBUG)
        );

        return $this->log;
    }

    /**
     * The path to the log file.
     *
     * @return string
     */
    protected function logPath()
    {
        return storage_path('ignite-data-encrypt.log');
    }
}
