<?php

namespace App\Jobs;

use Illuminate\Bus\Queueable;
use Illuminate\Queue\SerializesModels;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use App\Libs\Database\Eloquent\ContentTypeModel;

use Illuminate\Support\Facades\URL;
use GuzzleHttp\Client as GuzzleHttpClient;
use GuzzleHttp\Exception\RequestException;
use App\Models\GenericBackoffice\WebsiteObjectSearch;
use Throwable;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Log;

class IndexContent
{
    use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;

    public const ACTION_ADD               = 'add';
    public const ACTION_REMOVE            = 'remove';
    public const ACTION_REINDEX_RELATIONS = 'reindex_relations';

    /**
     * @var array Control structure to prevent cyclic reindexation of items
     */
    protected static $indexedItems = [];

    protected $model;

    /**
     * @var array contains the global indexer configuration
     */
    protected $config;
    /**
     * @var array contains the indexer configuration for the current model
     */
    protected $modelConfig;

    /**
     * @var array contains the data to be saved 
     */
    protected $saveData   = [];

    /**
     * @var string determines the action to take add or remove index
     */
    protected $actionType = 'add';

    /**
     * Create a new job instance.
     *
     * @return void
     */
    public function __construct(ContentTypeModel $model, string $type  = 'add')
    {
        $this->model      = $model;
        $this->config     = config('backoffice.indexer');
        $this->actionType = $type;
    }

    /**
     * Execute the job.
     *
     * @return void
     */
    public function handle()
    {

        $controlIndex = get_class($this->model) . '-' . $this->model->id;
        if (in_array($controlIndex, static::$indexedItems)) {
            return;
        }

        if ($this->actionType === static::ACTION_ADD) {
            $this->addToIndex($this->model);
            static::$indexedItems[] = $controlIndex;
        }

        if ($this->actionType === static::ACTION_REINDEX_RELATIONS) {
            $this->reindexRelations($this->model);
        }
    }


    /**
     * Reindexes the relation defined in the config for this content type
     * 
     * @param ContentTypeModel $model
     */
    protected function reindexRelations(ContentTypeModel $model): void
    {

        $itemClass         = get_class($model);
        if (!isset($this->config['throwsReindex'][$itemClass])) {
            return;
        }

        $this->modelConfig = $this->config['throwsReindex'][$itemClass];
        if (!isset($this->modelConfig['reindexes'])) {
            return;
        }
        foreach ($this->modelConfig['reindexes'] as $reindexableRelation) {

            $itemsToReindex = $model->$reindexableRelation;

            foreach ($itemsToReindex as $itemToReindex) {
                $this->addToIndex($itemToReindex);
            }
        }
    }



    /**
     * Adds a model to the indexer
     * 
     * @param ContentTypeModel $model
     */
    protected function addToIndex(ContentTypeModel $model): void
    {

        if (!$model->isIndexable) {
            return;
        }

        if ($model->status !== 'published') {
            $this->removeFromIndex($model);
            return;
        }

        $itemClass         = get_class($model);
        if (!isset($this->config['models'][$itemClass])) {
            return;
        }

        $this->modelConfig = $this->config['models'][$itemClass];
        if (!isset($this->modelConfig['fields']) || !isset($this->modelConfig['url'])) {
            return;
        }

        if (!is_array($this->modelConfig['fields'])) {
            return;
        }

        //Default Url to use
        $baseUrl     = URL::to($this->modelConfig['url']);
        $endpointUrl = sprintf('%s/%d', $baseUrl, $model->id);
        $langs       = getAvailableLanguageCodes();

        foreach ($langs as $lang) {
            $result = $this->makeCall($endpointUrl, $lang);

            if (!$result) {
                continue;
            }

            $this->processApiResponse($result, $lang, $model);
        }


        foreach ($this->saveData as $language => $data) {
            $websiteObjectSearch = WebsiteObjectSearch::where('website_object_id', $data['website_object_id'])
                ->where('language_id', $language)->first();
            if ($websiteObjectSearch) {
                if (empty($data['search_content'])) {
                    $websiteObjectSearch->delete();
                } else {
                    $websiteObjectSearch->title          = $data['title'];
                    $websiteObjectSearch->search_content = $data['search_content'];
                    $websiteObjectSearch->image          = $data['image'];
                    $websiteObjectSearch->url            = $data['url'] ?  $data['url'] : $endpointUrl;
                    $websiteObjectSearch->save();
                }
            } else {
                if (!empty($data['search_content']) && !is_null($data['website_object_id'])) {
                    $websiteObjectSearch                    = new WebsiteObjectSearch;
                    $websiteObjectSearch->language_id       = $language;
                    $websiteObjectSearch->website_object_id = $data['website_object_id'];
                    $websiteObjectSearch->title             = $data['title'];
                    $websiteObjectSearch->search_content    = $data['search_content'];
                    $websiteObjectSearch->url               = $data['url'] ?  $data['url'] : $endpointUrl;
                    $websiteObjectSearch->image             = $data['image'];
                    $websiteObjectSearch->save();
                }
            }
        }
    }

    /**
     * Removes a model to the indexer
     * 
     * @param ContentTypeModel $model
     */
    protected function removeFromIndex(ContentTypeModel $model): void
    {
        if (!$model->isIndexable) {
            return;
        }

        DB::table('website_objects_search')->where('website_object_id', $model->website_object_id)->delete();
    }


    /**
     * Processes the api response for an item in a certain lang and adds it to the save structure
     * @param array $itemData The data as returned fomr the API
     * @param string $lang The lang id
     * 
     * @return void
     */
    protected function processApiResponse($itemData, $lang, $model): void
    {
        $indexedContent = '';
        $fields         = $this->modelConfig['fields'];
        $relationFields = isset($this->modelConfig['relation_fields']) ? $this->modelConfig['relation_fields'] : [];
        $titleField     = isset($this->modelConfig['title_field']) ? $this->modelConfig['title_field'] : 'title';
        $blockFields    = isset($this->modelConfig['blockFields']) ? $this->modelConfig['blockFields'] : null;
        $image          = '';
        $imageField     = null;
        $urlField       = isset($this->modelConfig['url_field']) ? $this->modelConfig['url_field'] : null;
        $url            = null;

        if (isset($this->modelConfig['image_field'])) {
            $imageField = $this->modelConfig['image_field'];
        }

        foreach ($fields as $field) {

            if ($urlField && isset($itemData[$urlField])) {
                $url = $itemData[$urlField];
            }

            if (strpos($field, '.') !== false) {
                $fieldArr  = explode('.', $field);
                $mainField = $fieldArr[0];
                $subfield  = $fieldArr[1];

                if (isset($itemData[$mainField][$subfield]) && !empty($itemData[$mainField][$subfield])) {
                    $indexedContent .= $this->formatContent($itemData[$mainField][$subfield]);
                }
                continue;
            }

            if (!isset($itemData[$field])) {
                continue;
            }
            if (!empty($itemData[$field])) {
                $indexedContent .= $this->formatContent($itemData[$field]);
            }
        }

        if (is_array($relationFields)) {

            foreach ($relationFields as $relationField) {
                $arrField      = explode('.', $relationField);
                $relationName  = $arrField[0];
                $relationField = $arrField[1];

                if (isset($itemData[$relationName])) {

                    //Check to see if the attribute is there directly 
                    if (isset($itemData[$relationName][$relationField]) && !is_array($itemData[$relationName][$relationField])) {

                        if (empty($itemData[$relationName][$relationField])) {
                            continue;
                        }

                        $indexedContent .= $this->formatContent($itemData[$relationName][$relationField]);
                        continue;
                    }

                    //The attribute was no there directly, the assumption is that it is a list
                    foreach ($itemData[$relationName] as $relationItem) {
                        try {
                            if (!isset($relationItem[$relationField])) {
                                continue;
                            }
                            if (empty($relationItem[$relationField])) {
                                continue;
                            }
                            $indexedContent .=  $this->formatContent($relationItem[$relationField]);
                        } catch (Throwable $t) {
                            Log::error($t);
                        }
                    }
                }
            }
        }

        if ($imageField && isset($model->$imageField)) {
            $image = getMediaUrl($model->$imageField->filename);
        }



        //check if the item has block and it has configured block fields
        if (method_exists($model, 'blocks') && $blockFields) {
            $blocks = $model->blocks;
            foreach ($blocks as $block) {
                foreach ($blockFields as $blockField) {
                    if ($block->$blockField) {
                        $indexedContent .= $this->formatContent($block->$blockField);
                    }
                }
            }
        }


        $this->saveData[$lang] = [
            'title'             => isset($itemData[$titleField]) ? $itemData[$titleField] : '',
            'search_content'    => $indexedContent,
            'website_object_id' => $model->website_object_id,
            'image'             => $image,
            'url'               => $url,
        ];
    }


    /**
     * Makes a call to an API endpoint and returns the data contents or null
     * @param string $url
     * @param string $lang
     */
    protected  function makeCall(string $url, string $lang = 'pt'): ?array
    {

        $requestContent = [
            'headers' => [
                'Accept'       => 'application/json',
                'Content-Type' => 'application/json',
                'Lang-Code'    => $lang
            ]
        ];

        try {
            $client    = new GuzzleHttpClient();
            $apiRequest = $client->request('GET', $url, $requestContent);
            $response   = json_decode($apiRequest->getBody(), true);

            return isset($response['data']) ? $response['data'] : null;
        } catch (RequestException $re) {
            //nothing to do here
            Log::error('Error in geting content for indexation:' . $re->getMessage());
        }
        return null;
    }


    /**
     * Formats a string for content search inclusion
     */
    private function formatContent($content): string
    {
        return  !empty($content) ?  '<||>' . strip_tags($content) . '<||>' :  '';
    }
}
