<?php

namespace Ignite\Core\Console;

use Illuminate\Console\Command;
use Ignite\Core\Contracts\Repositories\ImportRepository;
use Ignite\Core\Entities\Import as ImportModel;
use Symfony\Component\Console\Input\InputOption;
use League\Csv\Writer;

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

    /**
     * The console command description.
     *
     * @var string
     */
    protected $description = 'Import data into Ignite from a CSV file.';

    /**
     * @var ImportRepository
     */
    protected $importRepository;

    /**
     * Create a new command instance.
     *
     * @param ImportRepository $importRepository
     */
    public function __construct(ImportRepository $importRepository)
    {
        parent::__construct();
        $this->importRepository = $importRepository;
    }

    /**
     * Execute the console command.
     *
     * @return mixed
     * @throws \Exception
     */
    public function handle()
    {
        $id = (int) $this->option('id');
        $user = (int) $this->option('user');
        $dryRun = $this->isDryRun();
        $import = $this->find($id);

        $imported = 0;
        $rejected = 0;
        $records = [];

        try {
            $importer = $import->resolveType();
            $this->info(sprintf('%s Importer', $import->getTypeLabel()));
            $this->confirm(
                sprintf(
                    'Running this process will import %s records. Are you sure you want to continue?',
                    $importer->count()
                ),
                false
            );

            if (! $dryRun) {
                $import->update(['status' => ImportModel::STATUS_PROCESSING]);
            }

            $log = $import->getLogger();
            $importer->prepare();
        } catch (\Exception $e) {
            return $this->handleException($e);
        }

        foreach ($importer->process() as $line => $data) {
            $source = array_merge($data, ['status' => 'rejected']);
            try {
                $validator = $importer->validate($data);

                if ($validator->fails()) {
                    throw new \DomainException(sprintf(
                        "Import record failed validation:\r\n%s",
                        json_encode($validator->errors()->toArray())
                    ));
                }

                $data = $importer->transform($data);

                if (! $dryRun) {
                    if ($importer->save($data)) {
                        $source['status'] = 'imported';
                    }
                }

                $log->info($importer->formatImportMessage($line, $data), $data);

                $imported++;
            } catch (\Exception $e) {
                $log->error($importer->formatRejectMessage($line, $data, $e), $data);
                $rejected++;
            }

            $records[$line] = $source;
        }

        if ($dryRun) {
            $this->info(sprintf('%s records would be imported, %s would be rejected.', $imported, $rejected));
        } else {
            // Overwrite all the records in the original file when all records have been imported.
            try {
                $writer = Writer::createFromPath($import->getFilePath(), 'w');
                $headers = $importer->getHeaders();
                $headers[] = 'status';
                array_unshift($records, $headers);
                $writer->insertAll($records);
            } catch (\Exception $e) {
                $this->error($e->getMessage());
            }

            $import->update([
                'run_by' => $user,
                'run_at' => now(),
                'imported' => $imported,
                'rejected' => $rejected,
                'status' => ImportModel::STATUS_COMPLETE
            ]);
            $this->info(sprintf('%s records were imported, %s were rejected.', $imported, $rejected));
        }
    }

    /**
     * Determine if user asked for a dry run.
     *
     * @return bool
     */
    protected function isDryRun()
    {
        return (in_array($this->option('dry-run'), [
            true, 'true',
            1, '1',
            'yes', 'y',
            null
        ], true));
    }

    /**
     * Get the console command arguments.
     *
     * @return array
     */
    protected function getArguments()
    {
        return [];
    }

    /**
     * Get the console command options.
     *
     * @return array
     */
    protected function getOptions()
    {
        return [
            ['id', null, InputOption::VALUE_OPTIONAL, 'The ID of an existing import record.', null],
            ['dry-run', null, InputOption::VALUE_OPTIONAL, 'Should we run the import or dry-run it.', false],
            ['user', null, InputOption::VALUE_OPTIONAL, 'The user running the import.', 1]
        ];
    }

    /**
     * Find an import record by ID.
     *
     * @param  int $id
     * @return \Ignite\Core\Entities\Import
     */
    protected function find($id)
    {
        return $this->importRepository->find($id);
    }

    /**
     * Handle an exception depending on the command verbosity.
     *
     * @param  \Exception $e
     * @throws \Exception
     */
    protected function handleException(\Exception $e)
    {
        if ($this->output->isVerbose() || $this->output->isVeryVerbose() || $this->output->isDebug()) {
            throw $e;
        }

        $this->error(
            sprintf('An error occurred: %s in %s on line %s', $e->getMessage(), $e->getFile(), $e->getLine())
        );
    }
}
