<?php

namespace Ignite\Core\Models\Form;

use Illuminate\Contracts\Validation\Validator;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Http\UploadedFile;
use Illuminate\Support\Arr;
use Illuminate\Support\Str;
use Illuminate\Validation\Rule;
use InvalidArgumentException;
use Kris\LaravelFormBuilder\Fields\FormField;

class Form extends \Kris\LaravelFormBuilder\Form
{
    /**
     * @var SchemaFieldDecorator
     */
    protected $fieldDecorator;

    /**
     * Determine if the user is viewing the form via the backend.
     *
     * @return bool
     */
    public function isViewingBackend()
    {
        return Str::contains(url()->current(), 'admin');
    }

    /**
     * Optionally mess with this form's $values before it's returned from getFieldValues().
     *
     * @param array $values
     * @return void
     */
    public function alterFieldValues(array &$values)
    {
        $values = $this->removeConfirmationFields($values);

        foreach ($values as $name => $value) {
            try {
                $field = $this->getField($name);
            } catch (InvalidArgumentException $e) {
                continue;
            }

            if (! empty($old = $this->getRequest()->old($name))) {
                $field->setOption('attr.data-initial', !isset($old) || is_scalar($old) ? $old : json_encode($old));
            }

            if ($sensitive = $field->getOption('sensitive')) {
                if (Str::contains($value, '*')) {
                    unset($values[$name]);
                    continue;
                }

                $value = encrypt($value);
            }

            if ($readonly = $field->getOption('readonly')) {
                unset($values[$name]);
                continue;
            }

            if ($field->getType() === 'file') {
                if ($values[$name] instanceof UploadedFile) {
                    $value = $this->handleFileUpload($field, $values[$name]);
                } elseif ($this->getRequest()->hasFile($field->getName())) {
                    $value = $this->handleFileUpload($field, $this->getRequest()->file($name));
                } else {
                    if ($parent = $field->getParent()) {
                        $value = Arr::get(
                            $this->getModel(),
                            $this->getFormHelper()->transformToDotSyntax($field->getName())
                        );
                    } else {
                        $value = $this->getFromModel($name, $values[$name]);
                    }
                }
                $field->setValue($value)->setRawValue($value);
            }

            $values[$name] = with(new Backend($value))->apply($field);

        }

        if (method_exists($this, 'groupByTable') && property_exists($this, 'groupByTable') && $this->groupByTable) {
            $this->groupByTable($values);
        }
    }

    protected function getFromModel($key, $default)
    {
        $model = $this->getModel();

        if ($model instanceof Model) {
            return $model->getAttribute($key);
        }

        if (is_array($model)) {
            return Arr::get($model, $key, $default);
        }

        return $default;
    }

    /**
     * Remove fields that only exist in the form to confirm other fields.
     *
     * @param  array $values
     * @return array
     */
    protected function removeConfirmationFields(array $values)
    {
        return collect($values)->filter(function ($value, $key) {
            return ! Str::contains($key, '_confirmation');
        })->toArray();
    }

    /**
     * Handle uploading files.
     *
     * @param FormField $field
     * @param UploadedFile $file
     *
     * @return string
     */
    protected function handleFileUpload(FormField $field, UploadedFile $file)
    {
        $disk = $field->getOption('disk', 'public');
        $path = $field->getOption('path', 'form/uploads');

        $filename = $file->getClientOriginalName();
        if ($fileOption = $field->getOption('store_as')) {
            $filename = is_callable($fileOption) ? $fileOption($file) : (string) $fileOption;
        }

        $file->storePubliclyAs($path, $filename, ['disk' => $disk]);

        return $filename;
    }

    /**
     * Render the field.
     *
     * @param  array       $field
     * @param  string|null $after
     *
     * @return self
     */
    protected function renderField(array $field, $after = null)
    {
        $data = $this->decorateField($field);

        if (is_null($after)) {
            $this->add($field['name'], $field['frontend_type'], $data);
        } else {
            $this->addAfter($after, $field['name'], $field['frontend_type'], $data);
        }

        return $this;
    }

    /**
     * Decorate the field.
     *
     * @param array $field
     *
     * @return array
     */
    protected function decorateField(array $field)
    {
        return $this->getFieldDecorator()->decorate($field);
    }

    /**
     * Cache the field decorator on the instance.
     *
     * @return SchemaFieldDecorator
     */
    protected function getFieldDecorator()
    {
        if (! $this->fieldDecorator) {
            $this->fieldDecorator = app(SchemaFieldDecorator::class, [
                'request' => $this->getRequest(),
                'model' => $this->model
            ]);
        }

        return $this->fieldDecorator;
    }

    /**
     * Validate the form.
     *
     * @param array $validationRules
     * @param array $messages
     *
     * @return Validator
     */
    public function validate($validationRules = [], $messages = [])
    {
        /** @var FormField $field */
        foreach ($this->fields as $name => $field) {
            if ($rules = $field->getValidationRules()->getRules()) {
                if (is_array($rules) && is_array($rules[$name])) {
                    foreach ($rules[$name] as $index => $rule) {
                        $validationRules[$name][$index] = $this->convertRule($name, $field, $rule);
                    }
                }
            }
        }

        return parent::validate($validationRules, $messages);
    }

    /**
     * Convert the rules.
     *
     * @param  string    $name
     * @param  FormField $field
     * @param  string    $rule
     * @return mixed
     */
    protected function convertRule(string $name, FormField $field, string $rule)
    {
        if (Str::contains($rule, 'unique') && ($this->model instanceof Model)) {
            return $this->convertRuleUnique($name, $field);
        }

        return $rule;
    }

    /**
     * Convert 'unique' rule when a model exists.
     *
     * @param  string    $name
     * @param  FormField $field
     *
     * @return \Illuminate\Validation\Rules\Unique
     */
    protected function convertRuleUnique($name, $field)
    {
        $table = $this->getTableName($field);

        $rule = Rule::unique($table, $name);
        $rule->ignore($this->model->getKey(), $this->model->getKeyName());

        return $rule;
    }

    /**
     * The table name for the field.
     *
     * @param FormField $field
     * @deprecated You should specify the exact table name, no guessing, remove once all existing sites are updated.
     * @return string
     */
    protected function getTableName($field)
    {
        $table = $field->getOption('table');

        if (! Str::contains($table, '_')) {
            return "core_$table";
        }

        return $table;
    }
}
