<?php

namespace Ignite\Core\Services\Importers;

use Illuminate\Support\Arr;
use Illuminate\Validation\Validator;
use Ignite\Core\Contracts\Importable;
use Ignite\Core\Models\Import\Importer as BaseImporter;
use Ignite\Core\Entities\Transaction;
use Ignite\Core\Repositories\TransactionRepository;

class Transactions extends BaseImporter implements Importable
{
    const USER_COLUMN = 'user_id';
    const TYPE_COLUMN = 'type';
    const VALUE_COLUMN = 'value';

    /**
     * The required data that must be available in the file.
     *
     * @var array
     */
    protected $validation = [
        'user_id'          => 'required',
        'type'             => 'required',
        'transaction_date' => 'date',
        'tax_date'         => 'date',
        'value'            => 'required|numeric',
    ];

    /**
     * The list of "known" fields.
     *
     * @var array
     */
    protected $fields = [
        'user_id' => ['email', 'account', 'user', 'employee_id'],
        'type' => [],
        'description' => [],
        'value' => ['amount'],
        'transaction_date' => ['transaction'],
        'tax_date' => ['tax'],
        'related_id' => [],
        'related_name' => [],
        'related_type' => [],
        'notes' => [],
    ];

    /**
     * @var TransactionRepository
     */
    protected $transactionRepository;

    /**
     * Construct an instance of the Transaction importer.
     *
     * @param TransactionRepository $transactionRepository
     */
    public function __construct(TransactionRepository $transactionRepository)
    {
        $this->transactionRepository = $transactionRepository;
    }

    /**
     * Return the column header in the csv file to use in order to identify the user.
     *
     * @return string
     */
    protected function getUserIdentifier()
    {
        return 'email';
    }

    /**
     * Prepare any dependencies for each iteration of the import process.
     *
     * @return void
     * @throws \League\Csv\Exception
     */
    public function prepare()
    {
        $this->cacheUsers(static::USER_COLUMN, $this->getUserIdentifier());
        $this->record->deleteLog();
    }

    /**
     * Validate a single line.
     *
     * @param  array $data
     * @return Validator
     */
    public function validate(array $data) : Validator
    {
        return $this->createValidatorInstance($data, $this->validation);
    }

    /**
     * Transform the data for the given line.
     *
     * @param  array $line
     * @return array
     */
    public function transform(array $line) : array
    {
        $line[$this->getUserIdentifier()] = $line[static::USER_COLUMN] ?? null;
        $line[static::USER_COLUMN] = $this->transformUser($line, static::USER_COLUMN);
        $line[static::TYPE_COLUMN] = $this->transformType($line);
        $line[static::VALUE_COLUMN] = $this->transformValue($line);
        $line['transaction_date'] = $this->transformDate($line, 'transaction_date');
        $line['tax_date'] = $this->transformDate($line, 'tax_date');

        return $line;
    }

    /**
     * Add a success message to the log file.
     *
     * @param  int   $line
     * @param  array $data
     * @return string
     */
    public function formatImportMessage(int $line, array $data) : string
    {
        return sprintf(
            'Imported line `%s` with %s `%s` for participant identified by %s `%s` and %s `%s` with transaction %s `%s`',
            $line,
            static::TYPE_COLUMN,
            Arr::get($data, static::TYPE_COLUMN, 'Missing'),
            static::USER_COLUMN,
            Arr::get($data, static::USER_COLUMN, 'Missing'),
            $this->getUserIdentifier(),
            Arr::get($data, $this->getUserIdentifier(), 'Missing'),
            static::VALUE_COLUMN,
            Arr::get($data, static::VALUE_COLUMN, 'Missing')
        );
    }

    /**
     * Add an error message to the log file.
     *
     * @param  int        $line
     * @param  array      $data
     * @param  \Exception $error
     * @return string
     */
    public function formatRejectMessage(int $line, array $data, \Exception $error) : string
    {
        return sprintf(
            'Rejected line `%s` with %s `%s` for participant identified by %s `%s` with transaction %s `%s` - Error: %s',
            $line,
            static::TYPE_COLUMN,
            Arr::get($data, static::TYPE_COLUMN, 'Missing'),
            $this->getUserIdentifier(),
            Arr::get($data, static::USER_COLUMN, 'Missing'),
            static::VALUE_COLUMN,
            Arr::get($data, static::VALUE_COLUMN, 'Missing'),
            $error->getMessage()
        );
    }

    /**
     * Process the data for a single line.
     *
     * @param  array $data
     * @return bool
     */
    public function save(array $data) : bool
    {
        $data = array_filter($data, function ($key) {
            return array_key_exists($key, $this->fields);
        }, ARRAY_FILTER_USE_KEY);

        $data['related_id'] = 0;
        $data['related_type'] = '';

        $transaction = new Transaction($data);

        return $transaction->save();
    }

    /**
     * Transform the Transaction type but throw an Exception with Unknown type.
     *
     * @param  array $line
     * @return string
     */
    protected function transformType($line)
    {
        $types = $this->transactionRepository->getAllowedTypes();

        if (! in_array($line[static::TYPE_COLUMN], $types)) {
            throw new \DomainException("Unknown Transaction type: `${line[static::TYPE_COLUMN]}`");
        }

        return $types[$line[static::TYPE_COLUMN]];
    }

    /**
     * Transform the Transaction value but throw an Exception with Unknown value.
     *
     * @param  array $line
     * @return int
     */
    protected function transformValue($line)
    {
        if (! isset($line[static::VALUE_COLUMN]) || empty($line[static::VALUE_COLUMN])) {
            throw new \DomainException('Missing value');
        }

        if (! is_numeric($line[static::VALUE_COLUMN])) {
            throw new \DomainException('Value must be numeric');
        }

        return (float) $line[static::VALUE_COLUMN];
    }
}
