<?php


namespace App\Libs\Services;


use App\Facades\ContentTypes;
use App\Jobs\ProcessSearch;
use App\Libs\Backoffice\Generic\ContentType;
use App\Libs\Contracts\Backoffice\ExternalField;
use App\Libs\Contracts\Backoffice\SavableField;
use App\Libs\Contracts\Backoffice\NonSavableField;

use App\Libs\Database\Eloquent\ContentTypeModel;
use App\Libs\Database\Eloquent\TranslationModel;
use App\Models\BackendAuth\BackendUser;
use App\Models\GenericBackoffice\Entry;
use App\Models\GenericBackoffice\EntryLanguage;
use App\Models\GenericBackoffice\Menu;
use App\Models\GenericBackoffice\MenuItem;
use App\Models\GenericBackoffice\MenuItemLanguage;
use App\Models\GenericBackoffice\WebsiteObject;
use App\Models\GenericBackoffice\WebsiteObjectLanguage;
use App\Models\GenericBackoffice\WebsiteObjectSearch;
use Illuminate\Support\Facades\DB;
use Mcamara\LaravelLocalization\Facades\LaravelLocalization;
use stdClass;
use Symfony\Component\HttpKernel\Exception\BadRequestHttpException;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
use Throwable;

class BackofficeService
{

    /**
     * Saves content type model
     *
     * @param  ContentType  $contentType
     * @param  ContentTypeModel  $model
     * @param  array  $formData
     * @param  BackendUser  $author
     * @param  array|boolean  $parentData
     * @param  int|boolean  $parentId
     * @param  boolean  $hasPivot
     * @return ContentTypeModel
     *
     * @throws Throwable
     */
    public function saveContentType(
        ContentType $contentType,
        ContentTypeModel $model,
        $formData,
        BackendUser $author,
        $parentData = false,
        $parentId = false,
        $hasPivot = false
    ) {

        if (!empty($parentData) && !$model->exists && !$hasPivot) {
            $parentField = $parentData["field"];
            $model->$parentField = $parentId;
        }

        $modelFields = $contentType->getFields()->getSingleLanguageFields();

        foreach ($modelFields as $field) {
            if ($field->isRelationField() || $field instanceof ExternalField || $field instanceof NonSavableField) {
                continue;
            }
            $fieldName = $field->getName();
            if ($field instanceof SavableField) {
                $field->save($model, $formData[$fieldName]);
            } else {
                $model->$fieldName = $formData[$fieldName];
            }

        }

        DB::beginTransaction();

        try {
            if (!$model->exists) {
                $model->backend_user_id = $author->id;
            }
            if ($model->exists || !$hasPivot) {
                $model->save();
            } else {
                $parentModel = $this->_getParentModelFromContentType($parentId, $parentData["contentType"]);
                $prop = $parentData["field"];
                $parentModel->$prop()->save($model);
            }

            $languageFields = $contentType->getFields()->getMultiLanguageFields();
            $languageModels = $model->languages;

            foreach ($languageFields as $field) {
                if ($field->isRelationField() || $field instanceof ExternalField || $field instanceof NonSavableField) {
                    continue;
                }

                $fieldName = $field->getName();
                $values = $formData[$fieldName];
                $languageModelType = $model->languages()->getRelated();

                foreach ($values as $languageId => $value) {
                    $languageModel = $languageModels->findByLocale($languageId);
                    if (empty($languageModel)) {
                        $languageModel = new $languageModelType;
                        $languageModel->language_id = $languageId;
                        $languageModels->add($languageModel);
                    }

                    if ($field instanceof SavableField) {
                        $field->save($languageModel, $value);
                    } else {
                        $languageModel->$fieldName = $value;
                    }
                }
            }

            $model->languages()->saveMany($languageModels->all());
            $websiteObjects = $model->websites;

            $websiteObject = $websiteObjects->first();

            if (empty($websiteObject)) {
                $websiteObject = new WebsiteObject();
                $websiteObject->content_type = $contentType->getType();

                if ($contentType->isOrderable()) {
                    $position = WebsiteObject::maxPosition(
                        get_class($model));
                    if (empty($position)) {
                        $position = 0;
                    }
                    $websiteObject->position = $position + 1;
                }

                $websiteObjects->add($websiteObject);
                foreach ($languageModels as $lang) {
                    $woLang = new WebsiteObjectLanguage();
                    $woLang->wo_language_id = $lang->language_id;

                    $websiteObject->languages->add($woLang);
                }
            }

            $defaultLang = getDefaultLanguage();
            foreach ($languageModels as $lang) {
                $woLang = $websiteObject->languages->findByLocale($lang->language_id);
                if (isset($formData["lang_slug"][$lang->language_id])) {
                    $woLang->lang_slug = $formData["lang_slug"][$lang->language_id];
                } else {
                    $woLang->lang_slug = $formData["lang_slug"][$defaultLang];
                }

                if ($defaultLang == $lang->language_id) {
                    $websiteObject->slug = $formData["lang_slug"][$lang->language_id];
                }

                $metaUpdateEnabled = config('backoffice.meta_info.update.enabled');
                $excludeUris = config('backoffice.meta_info.update.exclude_uris');
                if ($metaUpdateEnabled && !in_array($websiteObject->uri, $excludeUris)) {
                    $languageModel = $model->languages->findByLocale($lang->language_id);
                    $this->_insertMetaInfo($languageModel, $model);
                }
            }
            $websiteObject->status = $formData["status"];
            $websiteObject->template = $formData["template"];

            if ($contentType->hasTemplate() ||
                $contentType->getBaseUri() != false ||
                $contentType->getHasUri()) {
                $this->_updateUri($websiteObject, $contentType, false);
            }

            $model->websites()->saveMany($websiteObjects->all());

            foreach ($websiteObjects as $websiteObject) {
                $websiteObject->languages()->saveMany($websiteObject->languages->all());
            }

            $relationFields = $contentType->getFields()->getRelationFields();

            foreach ($relationFields as $field) {
                $fieldName = $field->getName();
                if (isset($formData[$fieldName])) {
                    $field->save($model, $formData[$fieldName]);
                }
            }

            DB::commit();
        } catch (Throwable $e) {
            DB::rollback();
            throw($e);
        }

        if (!empty($contentType->getSearch())) {
            ProcessSearch::dispatch($contentType, $model);
        }

        return $model;
    }

    /**
     * @param int $parentId
     * @param string $contentType
     * @return ContentTypeModel|null
     */
    protected function _getParentModelFromContentType(int $parentId, string $contentType): ?ContentTypeModel
    {
        $parentContentType = ContentTypes::find($contentType);
        $parentModelType = $parentContentType->getModelType();
        return $parentModelType::find($parentId);
    }

    /**
     * Inserts meta info if not set, in models that have those properties
     * If the model has a parent, sets with the parent's info
     * If not, sets with the info of the page defined in config
     *
     * @param TranslationModel $languageModel
     * @param ContentTypeModel $model
     */
    protected function _insertMetaInfo(TranslationModel $languageModel, ContentTypeModel $model)
    {
        $config = config('backoffice.meta_info.update');
        $mustSave = false;
        if (property_exists($languageModel, 'meta_title') && empty($languageModel->meta_title)) {
            $metaTitleSourceField = config('backoffice.generic.'.$model->content_type.'.meta_title_update_field');
            $metaTitle = $metaTitleSourceField ? $languageModel->$metaTitleSourceField : $languageModel->title;
            $languageModel->meta_title = $metaTitle . ' - ' . $config['title_extra'];
            $mustSave = true;
        }

        if (property_exists($languageModel, 'meta_description') &&
            property_exists($languageModel, 'meta_keywords') &&
            (empty($languageModel->meta_description) || empty($languageModel->meta_keywords))) {
            if (!is_null($model->parent_id)) {
                $parentModel = $this->_getParentModelFromContentType($model->parent_id, $model->content_type);
                $parentLanguageModel = $parentModel->languages->findByLocale($languageModel->language_id);
                $description = $parentLanguageModel->meta_description;
                $keywords = $parentLanguageModel->meta_keywords;
            } else {
                $baseUri = $config['no_parent_source'];
                $wsObject = WebsiteObject::where('uri', $baseUri)->first();
                $description = "";
                $keywords = "";
                if ($wsObject) {
                    $sourceLanguageModel = $wsObject->objectable;
                    $description = $sourceLanguageModel->meta_description;
                    $keywords = $sourceLanguageModel->meta_keywords;
                }
                if (empty($description)) {
                    $description = $config['title_extra'];
                }
                if (empty($keywords)) {
                    $keywords = $config['title_extra'];
                }
            }

            if (empty($languageModel->meta_description)) {
                $languageModel->meta_description = $description;
            }
            if (empty($languageModel->meta_keywords)) {
                $languageModel->meta_keywords = $keywords;
            }
            $mustSave = true;
        }

        if ($mustSave) {
            $languageModel->save();
        }
    }

    /**
     * Updated the Uri on websiteobject and it's languages
     *
     * @param  WebsiteObject  $websiteObject
     * @param  ContentType  $contentType
     * @param  bool  $save
     */
    protected function _updateUri(WebsiteObject $websiteObject, ContentType $contentType, $save = true)
    {
        $uri = $contentType->getBaseUri();
        $languages = getAvailableLanguages();
        $defaultLang = getDefaultLanguage();

        $defaultUri = "";
        if ($websiteObject->parent) {
            $parent = $websiteObject->parent;

            foreach ($languages as $langCode => $lang) {
                $parentWoLang = $parent->languages->findByLocale($langCode);
                $woLang = $websiteObject->languages->findByLocale($langCode);
                $woLang->lang_uri = $parentWoLang->lang_uri.'/'.$woLang->lang_slug;
                if ($defaultLang == $langCode) {
                    $defaultUri = $woLang->lang_uri;
                }
                if ($save) {
                    $woLang->save();
                }
            }
        } else {
            if ($websiteObject->slug == 'home') {
                $defaultUri = '/';
                foreach ($languages as $langCode => $lang) {
                    $woLang = $websiteObject->languages->findByLocale($langCode);
                    $woLang->lang_uri = $defaultUri;
                    if ($save) {
                        $woLang->save();
                    }
                }
            } else {
                foreach ($languages as $langCode => $lang) {
                    $woLang = $websiteObject->languages->findByLocale($langCode);

                    if (is_array($uri)) {
                        $uriLang = $uri[$langCode];
                    } else {
                        $uriLang = $uri;
                    }
                    $woLang->lang_uri = $uriLang.'/'.$woLang->lang_slug;
                    if ($defaultLang == $langCode) {
                        $defaultUri = $woLang->lang_uri;
                    }
                    if ($save) {
                        $woLang->save();
                    }
                }
            }
        }
        $websiteObject->uri = $defaultUri;
        if ($save) {
            $websiteObject->save();
        }

        if ($websiteObject->website_object_id) {
            $children = $websiteObject->children;
            foreach ($children as $child) {
                $this->_updateUri($child, $contentType, $save);
            }
        }
    }

    /**
     * Generates a slug or returns false in case is not possible
     *
     * @param  string  $originSlug
     * @param  string  $langId
     * @return bool|string
     */
    public function generateSlug($originSlug, $langId)
    {
        $originSlug = urlize($originSlug);
        $slug = $originSlug;

        $slugCounter = 1;
        while (WebsiteObject::hasSlug($slug, $langId) && $slugCounter <= 100) {
            $slug = $originSlug."-".$slugCounter;
            $slugCounter++;
        }

        if ($slugCounter == 100) {
            return false;
        } else {
            return $slug;
        }
    }

    /**
     * Deletes a content type entry
     *
     * @param  ContentType  $contentType
     * @param  int  $id
     *
     * @return ContentTypeModel
     *
     * @throws NotFoundHttpException
     * @throws Throwable
     */
    public function deleteContentType(ContentType $contentType, $id)
    {
        $modelType = $contentType->getModelType();

        $model = $modelType::find($id);

        if (is_null($model)) {
            throw new NotFoundHttpException();
        }

        DB::beginTransaction();

        try {
            $model->delete();
            WebsiteObject::where("object_id", $id)
                ->where("object_type", $modelType)
                ->delete();
            $this->_cleanWebsiteObjects();
            $this->_cleanWebsiteObjectSearches();
            DB::commit();
        } catch (Throwable $e) {
            DB::rollback();
            throw $e;
        }

        return $model;
    }

    /**
     * Cleans website objects that are not in the main tables
     */
    protected function _cleanWebsiteObjects()
    {
        $contentTypes = ContentTypes::all();

        foreach ($contentTypes as $contentType) {
            $modelType = $contentType->getModelType();
            $model = new $modelType();
            $table = $model->getTable();

            WebsiteObject::where('content_type', $contentType->getType())
                ->whereNotExists(function ($query) use ($table) {
                    $query->select(DB::raw(1))
                        ->from($table)
                        ->whereRaw("website_objects.object_id = {$table}.id");
                })->delete();
        }
    }

    /**
     * Cleans website object searches that are not in the main tables
     */
    protected function _cleanWebsiteObjectSearches()
    {
        DB::table("website_objects_search")
            ->whereNotExists(function ($query) {
                $query->select(DB::raw(1))
                    ->from("website_objects")
                    ->whereRaw("website_objects_search.website_object_id = website_objects.website_object_id");
            })->delete();
    }

    /**
     * Commit the new order in the website objects
     *
     * @param  ContentType  $contentType
     * @param  array  $orderTree
     *
     * @throws BadRequestHttpException
     * @throws Throwable
     */
    public function reorderContentType(ContentType $contentType, $orderTree)
    {
        DB::beginTransaction();
        try {
            $counter = 0;
            $this->_saveContentTypeOrder($orderTree, $contentType->getModelType(), $counter);
            DB::commit();
        } catch (Throwable $e) {
            DB::rollback();
            throw $e;
        }
    }

    /**
     * Saves the new order in the website objects
     *
     * @param  array  $orderTree
     * @param  string  $objectType
     * @param  int  $i
     * @param  object|null  $parent
     *
     * @throws BadRequestHttpException
     */
    protected function _saveContentTypeOrder($orderTree, $objectType, &$i, $parent = null)
    {
        foreach ($orderTree as $orderItem) {
            $i++;
            if (!is_numeric($orderItem->id)) {
                throw new BadRequestHttpException();
            }
            $websiteObject = WebsiteObject::find($orderItem->id);
            if (empty($websiteObject) || $websiteObject->object_type !== $objectType) {
                throw new BadRequestHttpException();
            }
            $websiteObject->position = $i;
            if ($parent) {
                $websiteObject->parent_id = $parent->id;
                if ($websiteObject->object_type == 'Entry') {
                    $websiteObject->uri = $websiteObject->parent->uri.'/'.$websiteObject->slug;
                }
            } else {
                if ($websiteObject->object_type == 'Entry') {
                    $websiteObject->uri = '/'.($websiteObject->slug == 'home' ? '' : $websiteObject->slug);
                }
                $websiteObject->parent_id = null;
            }
            $websiteObject->save();

            if (isset($orderItem->children)) {
                $this->_saveContentTypeOrder($orderItem->children, $objectType, $i, $orderItem);
            }
        }
    }

    /**
     * Creates a content type template data
     *
     * @param  ContentType  $contentType
     * @param  int  $parentId
     * @param  BackendUser  $author
     *
     * @return ContentTypeModel
     *
     * @throws NotFoundHttpException
     * @throws Throwable
     */
    public function createContentTypeTemplate(ContentType $contentType, $parentId, BackendUser $author)
    {
        $modelType = $contentType->getModelType();
        $model = new $modelType;

        $parentData = $contentType->getParent();
        $parentContentType = ContentTypes::find($parentData["contentType"]);
        $parentModelType = $parentContentType->getModelType();
        $parentModel = $parentModelType::find($parentId);
        $fkName = $parentModel->getForeignKey();

        if (empty($parentModel)) {
            throw new NotFoundHttpException();
        }

        DB::beginTransaction();

        try {
            $model->$fkName = $parentId;
            $model->backend_user_id = $author->id;
            $model->save();

            $languageModels = $model->languages;
            $languageModelType = $model->languages()->getRelated();

            foreach ($parentModel->languages as $parentLangModel) {
                $languageModel = new $languageModelType;
                $languageModel->language_id = $parentLangModel->language_id;
                $languageModel->title = $parentLangModel->title;
                $languageModels->add($languageModel);
            }

            $model->languages()->saveMany($languageModels->all());

            $websiteObjects = $model->websites;
            $parentWebsiteObject = $parentModel->websites->first();

            $websiteObject = new WebsiteObject();
            $websiteObject->content_type = $contentType->getType();

            $slug = $parentWebsiteObject->slug."-conf-".$parentWebsiteObject->id;
            $websiteObject->slug = $slug;
            $websiteObject->status = $parentWebsiteObject->status;
            $websiteObjects->add($websiteObject);

            $model->websites()->saveMany($websiteObjects->all());

            foreach ($parentModel->languages as $langModel) {
                $woLang = new WebsiteObjectLanguage();
                $woLang->website_object_id = $websiteObject->id;
                $woLang->wo_language_id = $langModel->language_id;
                $woLang->lang_slug = $slug;
                $woLang->lang_uri = null;

                $woLang->save();
            }

            DB::commit();
        } catch (Throwable $e) {
            DB::rollback();
            throw $e;
        }

        return $model;
    }

    /**
     * Returns a dummy MenuItem as stdClass
     *
     * @param  array  $data
     * @return stdClass
     */
    public function createTempMenuItem($data)
    {
        $languages = LaravelLocalization::getSupportedLanguagesKeys();
        $defaultLanguage = config('app.locale');
        $tempMenuItem = new stdClass();
        if ($data['type'] === 'page') {

            $page = Entry::find($data['page_id']);

            //$tempMenuItem->url = $page->uri;
            $tempMenuItem->target = null;
            $tempMenuItem->active = $page->status == 'published';
            $tempMenuItem->id = 'new_'.rand(0, 3000000);
            $tempMenuItem->entry = $page;
            $tempMenuItem->languages = array();
            foreach ($languages as $language) {
                $itemLang = new stdClass();
                $itemLang->language_id = $language;
                $pageLanguage = $page->languages->findByLocale($language);
                $pageWO = $page->getActiveWebsiteEntry();
                if ($pageLanguage) {
                    $itemLang->title = $pageLanguage->title;
                } else {
                    $itemLang->title = '';
                }
                if ($pageWO) {
                    $pageWOLang = $pageWO->languages->findBylocale($language);
                    $itemLang->url = $pageWOLang->lang_uri;
                }
                $tempMenuItem->languages[] = $itemLang;

                if ($language == $defaultLanguage) {
                    $tempMenuItem->title = $pageLanguage->title;
                }
            }
        } else {

            //$tempMenuItem->url = $data['url'];
            $tempMenuItem->target = $data['new_window'] ? '_blank' : null;
            $tempMenuItem->active = true;
            $tempMenuItem->id = 'new_'.rand(0, 3000000);
            $tempMenuItem->entry = null;
            $tempMenuItem->languages = array();
            foreach ($data['title'] as $lang => $value) {
                $itemLang = new stdClass();
                $itemLang->language_id = $lang;
                $itemLang->title = $value;
                $itemLang->url = $data['url'][$lang];
                $tempMenuItem->languages[] = $itemLang;

                if ($lang == $defaultLanguage) {
                    $tempMenuItem->title = $value;
                }
            }
        }

        return $tempMenuItem;
    }

    /**
     * Build menu structure for FB insertion
     *
     * @param  array  $menuStructure
     * @param  int  $i
     * @param  array  $data
     * @param  null|int  $parentId
     */
    public function buildStructure(&$menuStructure, &$i, &$data, $parentId = null)
    {

        foreach ($menuStructure as $menuItem) {
            $i++;
            $menuItem->title = $data['titles'][$menuItem->id]['en'];
            $menuItem->position = $i;
            $menuItem->parent_id = $parentId;

            $pageWO = null;
            if ($data['types'][$menuItem->id] == 'page') {
                $page = Entry::find($data['entry_id'][$menuItem->id]);

                if (is_null($page)) {
                    //Todo: some error action?
                }
                $menuItem->entry = $page;
                $pageWO = $menuItem->entry->getActiveWebsiteEntry();
                //$menuItem->url = $page->uri;
            } else {
                //$menuItem->url = $data['urls'][$menuItem->id];
                $menuItem->entry = null;
            }
            $menuItem->target = isset($data['new_windows'][$menuItem->id]) ? '_blank' : null;
            $menuItem->active = isset($data['states'][$menuItem->id]) ? true : false;
            $languages = LaravelLocalization::getSupportedLanguagesKeys();
            $menuItem->languages = array();
            foreach ($languages as $language) {
                $itemLang = new stdClass();
                $itemLang->language_id = $language;
                $itemLang->title = $data['titles'][$menuItem->id][$language];

                $itemLang->url = $data['urls'][$menuItem->id][$language];
                if ($pageWO) {
                    $pageWOLang = $pageWO->languages->findByLocale($language);
                    $itemLang->url = $pageWOLang->lang_uri;
                } else {
                    $itemLang->url = $data['urls'][$menuItem->id][$language];
                }

                $menuItem->languages[] = $itemLang;

                if ($language == config('app.locale')) {
                    $menuItem->title = $data['titles'][$menuItem->id][$language];
                }
            }

            //$i++;
            if (isset($menuItem->children)) {
                $this->buildStructure($menuItem->children, $i, $data, $menuItem->id);
            }
        }
    }


    /**
     * Save the menu structure to the DB
     *
     * @param  Menu  $menu
     * @param  array  $menuStructure
     * @param  boolean  $toDelete
     * @return array
     */
    public function saveMenu($menu, $menuStructure, $toDelete)
    {
        DB::transaction(function () use ($menu, $menuStructure, $toDelete) {
            $this->_saveMenuStructure($menu->id, $menuStructure);

            //deletes excluded menu items
            if ($toDelete) {
                $toDelete = explode(',', $toDelete);
                foreach ($toDelete as $id) {
                    if (is_numeric($id)) {
                        $menu->menuItems()->find($id)->delete();
                    }
                }
            }
        });

        return $menuStructure;
    }

    /**
     * Saves a level of the menu structure
     *
     * @param  int  $menuId
     * @param  array  $menuStructure
     * @param  null|int  $parentId
     */
    private function _saveMenuStructure($menuId, &$menuStructure, $parentId = null)
    {
        foreach ($menuStructure as $item) {
            if (strpos($item->id, 'new_') !== false) {
                //New MenuITem
                $menuItem = new MenuItem();
            } else {
                //Existing MenuItem
                $menuItem = MenuItem::find($item->id);
                //Reset langugage fields
                MenuItemLanguage::where('menu_item_id', $item->id)->delete();
            }
            $menuItem->parent_id = $parentId;
            $menuItem->menu_id = $menuId;
            $menuItem->target = $item->target;
            $menuItem->position = $item->position;
            $menuItem->active = $item->active;


            //Languae fields
            $langFields = array();
            foreach ($item->languages as $language) {
                $menuItemLang = new MenuItemLanguage(array(
                    'language_id' => $language->language_id,
                    'title' => $language->title
                ));
                if (isset($item->entry) && $item->entry) {
                    //Page item
                    $menuItem->entry_id = $item->entry->id;
                    $entryWO = $item->entry->getActiveWebsiteEntry();
                    $entryWOLang = $entryWO->languages->findByLocale($language->language_id);
                    $menuItemLang->url = $entryWOLang->lang_uri;
                } else {
                    //Link Item
                    $menuItemLang->url = $language->url;
                }
                $langFields[] = $menuItemLang;
            }

            $menuItem->save();
            $menuItem->languages()->saveMany($langFields);

            if (isset($item->children)) {
                $this->_saveMenuStructure($menuId, $item->children, $menuItem->id);
            }
        }
    }
}
