<?php

namespace App\Libs\Backoffice\Fields;

use App\Facades\Backoffice\ContentTypes;
use App\Libs\Backoffice\Contracts\ListableField;
use App\Libs\Backoffice\Exceptions\InvalidConfigurationException;
use App\Libs\Backoffice\Exceptions\InvalidMethodException;
use App\Libs\Backoffice\Exceptions\InvalidTypeException;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Arr;
use Illuminate\Support\Facades\Schema;
use Illuminate\Support\Str;
use Mcamara\LaravelLocalization\Facades\LaravelLocalization;

class SelectBoxField extends BaseField implements ListableField
{
    protected $contentType;
    protected $contentField;
    protected $model;
    protected $list;
    protected $defaultValue;
    protected $allowInsert = false;
    protected $boxSearch = false;
    protected $labelField = null;
    protected $mandatory = false;
    protected $selectModel;

    public function __construct($config, $model)
    {
        $model = $model;
        parent::__construct($config);

        if (empty($config["contentType"]) && empty($config["list"]) && empty($config['selectModel'])) {
            $fieldName = ucfirst(Str::camel($this->name));
            $methodName = "get" . $fieldName . "Options";
            if(class_exists($model)) {
                if (method_exists($model, $methodName) && is_callable($model, $methodName)) {
                    $list = call_user_func($model . '::' . $methodName);
                    if (gettype($list) !== 'array') {
                        throw new InvalidTypeException("The method '{$methodName}' should return an array");
                    }
                    $config['list'] = $list;
                } else {
                    throw new InvalidMethodException("The model '{$model}' does not have a static '{$methodName}' function defined");
                }
            } else {
                throw new InvalidConfigurationException("The field {$this->getName()} is missing a content type, a list, a model or a static '{$methodName}' function");
            }
        }

        $configChoices = (int)!empty($config["contentType"]) + (int)!empty($config["list"]) + (int)!empty($config["selectModel"]);
        if ($configChoices > 1) {
            throw new InvalidConfigurationException("The field {$this->getName()} can only have a content type, a list or a model");
        }

        if (!empty($config["contentType"])) {
            $this->contentType = $config["contentType"];
        } elseif (!empty($config["selectModel"])) {
            $this->selectModel = $config["selectModel"];
        } else {
            $this->list = $config["list"];
        }

        if (!empty($config["contentField"])) {
            $this->contentField = $config["contentField"];
        }

        if (isset($config["defaultValue"])) {
            $this->defaultValue = $config["defaultValue"];
        }

        if (isset($config["mandatory"])) {
            $this->mandatory = $config["mandatory"];
        }

        if (!empty($config["labelField"])) {
            $this->labelField = $config["labelField"];
        }

        if (!empty($config["allowInsert"]) && !empty($config["contentType"])) {
            $this->allowInsert = $config["allowInsert"];
        }

        if (!empty($config["boxSearch"])) {
            $this->boxSearch = $config["boxSearch"];
        }
    }

    public function getContentType()
    {
        return $this->contentType;
    }

    public function setContentType($contentType)
    {
        $this->contentType = $contentType;
    }

    public function getContentField()
    {
        return $this->contentField;
    }

    public function setContentField($contentField)
    {
        $this->contentField = $contentField;
    }

    public function getList()
    {
        return $this->list;
    }

    public function setList($list)
    {
        $this->list = $list;
    }

    public function getSelectModel()
    {
        return $this->selectModel;
    }

    public function setSelectModel($selectModel)
    {
        return $this->selectModel = $selectModel;
    }

    public function getAllowInsert()
    {
        return $this->allowInsert;
    }

    public function setAllowInsert($allowInsert)
    {
        $this->allowInsert = $allowInsert;
    }

    public function isBoxSearchable()
    {
        return $this->boxSearch;
    }

    public function setBoxSearchable($boxSearch)
    {
        $this->boxSearch = $boxSearch;
    }

    public function isMandatory()
    {
        return $this->mandatory;
    }

    public function setMandatory($mandatory = false)
    {
        $this->mandatory = $mandatory;
    }

    public function buildSelect(Builder $query)
    {
        if (!empty($this->contentType)) {
            return $this->_buildModelSelect($query);
        } else {
            return $this->_buildListSelect($query);
        }
    }

    private function _buildModelSelect(Builder $query)
    {
        $field = $this->getListField();
        $tablePrefix = $query->getModel()->getTable();
        $model = $this->_getModel();
        $modelTable = $model->getTable();
        $query->leftJoin($modelTable, "{$tablePrefix}.{$this->getName()}", "=", "{$modelTable}.id");

        $query->addSelect("{$modelTable}.id as ".$this->getName()."_id");

        $schema = Schema::getColumnListing($modelTable);
        $fieldExistsInModel = in_array($field, $schema);

        if ($fieldExistsInModel) {
            $query->addSelect("{$modelTable}.{$field} as ".$this->getName());
        } else {
            $languageRelationship = $model->languages();
            $languageModel = $languageRelationship->getRelated();
            $languageFK = $languageRelationship->getQualifiedForeignKeyName();
            $query->leftJoin(
                $languageModel->getTable(),
                function ($join) use ($model, $languageFK, $languageModel) {
                    $join->on($model->getQualifiedKeyName(), "=", $languageFK)
                        ->where("{$languageModel->getTable()}.language_id", "=", LaravelLocalization::getCurrentLocale());
                }
            );

            $query->addSelect("{$languageModel->getTable()}.{$field} as ".$this->getName());
        }

        return $query;
    }

    private function _buildListSelect(Builder $query)
    {
        if ($this->isMultiLanguage()) {
            $tablePrefix = $query->getModel()->languages()->getRelated()->getTable();
        } else {
            $tablePrefix = $query->getModel()->getTable();
        }

        $query->addSelect("{$tablePrefix}.{$this->name}");
        return $query;
    }

    public function buildWhere(Builder $query, $searchPhrase)
    {
        if (empty($searchPhrase) || !$this->isSearchable()) {
            return $query;
        }

        if (!empty($this->contentType)) {
            return $this->_buildModelWhere($query, $searchPhrase);
        } else {
            return $this->_buildListWhere($query, $searchPhrase);
        }
    }

    private function _buildModelWhere(Builder $query, $searchPhrase)
    {
        $field = $this->getListField();
        $model = $this->_getModel();
        $modelTable = $model->getTable();

        $schema = Schema::getColumnListing($modelTable);
        $fieldExistsInModel = in_array($field, $schema);

        if ($fieldExistsInModel) {
            $query->orWhere("{$modelTable}.{$field}", 'like', "%{$searchPhrase}%");
        } else {
            $tablePrefix = $this->_getModel()->languages()->getRelated()->getTable();
            $query->orWhere("{$tablePrefix}.{$field}", 'like', "%{$searchPhrase}%");
        }

        return $query;
    }

    private function _buildListWhere(Builder $query, $searchPhrase)
    {
        $list = Arr::where($this->list, function ($key, $value) use ($searchPhrase) {
            return Str::contains(strtolower($value), strtolower($searchPhrase));
        });
        $tablePrefix = $query->getModel()->getTable();
        if (!empty($list)) {
            $query->orWhereIn("{$tablePrefix}.{$this->getName()}", array_keys($list));
        }
        return $query;
    }

    public function buildOrder(Builder $query, $sortOrder = "asc")
    {
        if (!empty($this->contentType)) {
            return $this->_buildModelOrder($query, $sortOrder);
        } else {
            return $this->_buildListOrder($query, $sortOrder);
        }
    }

    private function _buildModelOrder(Builder $query, $sortOrder = "asc")
    {
        $field = $this->getListField();
        $model = $this->_getModel();
        $modelTable = $model->getTable();

        $schema = Schema::getColumnListing($modelTable);
        $fieldExistsInModel = in_array($field, $schema);

        if ($fieldExistsInModel) {
            $query->orderBy("{$modelTable}.{$field}", $sortOrder);
        } else {
            $tablePrefix = $this->_getModel()->languages()->getRelated()->getTable();
            $query->orderBy("{$tablePrefix}.{$field}", $sortOrder);
        }

        return $query;
    }

    private function _buildListOrder(Builder $query, $sortOrder = "asc")
    {
        $list = $this->list;
        $sortFunction = "_reorderAscList";
        if (strtolower($sortOrder) != 'asc') {
            $sortFunction = "_reorderDescList";
        }
        $tablePrefix = $query->getModel()->getTable();

        uasort($list, array($this, $sortFunction));
        $sortValues = implode(" ','", array_keys($list));
        $query->orderByRaw("field({$tablePrefix}.{$this->getName()}, '{$sortValues}')");

        return $query;
    }

    protected function _reorderAscList($listItem1, $listItem2)
    {
        $firstItem = strtolower($listItem1);
        $secondItem = strtolower($listItem2);

        if ($firstItem == $secondItem) {
            return 0;
        }

        return ($firstItem > $secondItem) ? 1 : -1;
    }

    protected function _reorderDescList($listItem1, $listItem2)
    {
        $firstItem = strtolower($listItem1);
        $secondItem = strtolower($listItem2);

        if ($firstItem == $secondItem) {
            return 0;
        }

        return ($firstItem < $secondItem) ? 1 : -1;
    }

    public function getListData()
    {
        return [
            'name' => $this->getName(),
            'type' => ucfirst($this->getType()),
            'label' => ucfirst($this->getLabel()),
            'isStaticList' => !empty($this->list)
        ];
    }

    /**
     * Gets the content type model instance
     *
     * @return Model
     */
    private function _getModel()
    {
        if (empty($this->model)) {
            $contentType = ContentTypes::find($this->contentType);
            $modelType = $contentType->getModelType();
            $this->model = new $modelType;

        }

        return $this->model;
    }

    public function getCurrentValue(Model $model)
    {
        $fieldName = $this->getName();
        if (request()->has($fieldName)) {
            return request()->input($fieldName);
        }

        if ($model->exists) {
            return $model->$fieldName;
        }

        if (!empty($this->defaultValue)) {
            return $this->defaultValue;
        }

        return null;
    }

    public function getData(Model | null $model, $enabled = true, $hidden = false)
    {
        $list = $this->_fetchList();

        $name = $this->getName();
        $value = null;

        if ($this->isMultiLanguage()) {
            $locales = LaravelLocalization::getSupportedLocales();

            foreach ($locales as $locale => $localeData) {
                if ($model && $model->exists) {
                    $languageModel = $model->languages->findByLocale($locale);

                    if (!empty($languageModel)) {
                        $value[$locale] = $languageModel->$name;
                    } else {
                        $value[$locale] = '';
                    }
                } else {
                    $value[$locale] = $this->defaultValue ? $this->defaultValue : '';
                }
            }
        } else {
            if ($model && $model->exists) {
                $value = $model->$name;
            } elseif ($this->defaultValue) {
                $value = $this->defaultValue;
            }
        }

        $contentType = false;

        if ($this->getContentType()) {
            $contentType = ContentTypes::find($this->getContentType());
            $contentType = array(
                "type" => $contentType->getType(),
                "label" => ucfirst($contentType->getSingularLabel())
            );
        }

        if ($this->isDisabled()) {
            $enabled = false;
        }

        return [
            'name' => $this->getName(),
            'type' => ucfirst($this->getType()),
            'isMultilanguage' => $this->isMultiLanguage(),
            'list' => $list,
            'label' => $this->getLabel(),
            'info' => $this->getInfo(),
            'value' => $value,
            'required' => $this->isMandatory(),
            'hide' => $hidden,
            'enabled' => $enabled,
            'conditions' => $this->conditions,
            'allowInsert' => $this->allowInsert,
            'contentType' => $contentType,
            'selectModel' => $this->selectModel,
            'contentField' => $this->contentField,
            'isSearchable' => $this->isBoxSearchable(),
        ];
    }

    private function _fetchList()
    {
        $list = [];
        if (!empty($this->list)) {
            $list = $this->list;
        } else {
            /*
            $relationshipModel = $this->_getModel();
            $relationshipTable = $relationshipModel->getTable();

            $languageRelationship = $relationshipModel->languages();
            $languageModel = $languageRelationship->getRelated();
            $languageTable = $languageModel->getTable();

            if ($this->labelField == null) {
                $this->labelField = $languageTable.'.title';
            }

            $list = $relationshipModel->full()
                ->select("{$relationshipTable}.id", $this->labelField)
                ->orderBy("{$languageTable}.title", "asc")->pluck($this->labelField, "id")->all();
            */
        }
        return array_map(function ($value, $label) {
            return [
                'label' => $label,
                'value' => $value
            ];
        }, array_keys($list), array_values($list));
    }

    public function getValidationDataValues($copyAllLang = false, $input = null)
    {
        $fieldName = $this->getName();
        if ($input) {
            $values = $input[$fieldName];
        } else {
            $values = request()->get($fieldName);
        }

        if ($values == "") {
            $values = null;
        }

        return $values;
    }

    public function toArray()
    {
        $array = parent::toArray();
        if (!empty($this->list)) {
            $array["formatter"] = 'list';
            $array["list"] = $this->list;
        } elseif (!empty($this->contentType)) {
            $array["formatter"] = 'relationship';
            $array["contentType"] = $this->contentType;
        }
        return $array;
    }

    /**
     * Simple level 2 search indexing. only works for multilang and for title field
     * Not to be used in any outher case!
     *
     * @param  Model  $model
     * @param  boolean  $view
     *
     * @return array
     */
    public function getSearchStructure(Model $model, $view = false)
    {
        $searchData = array();

        $fieldName = $this->getName();

        if (!$this->contentType || !$model->$fieldName) {
            return null;
        }

        $childModel = $this->_getModel();
        $childModel = $childModel::find($model->$fieldName);

        foreach ($childModel->languages as $languageModel) {
            if (!empty($languageModel->title)) {
                if ($view) {
                    $value = cut_string($languageModel->title, 500);
                } else {
                    $value = $languageModel->title;
                }
                $searchData[$languageModel->language_id] = $value;
            } else {
                $searchData[$languageModel->language_id] = '';
            }
        }
        return $searchData;
    }
}


