<?php

namespace App\Library\Search;

//use App\Library\Util\SolrSearchQuery;
use App\Library\Search\SearchResult;
use App\Library\Search\SolariumDocument;
use App\Library\Util\SearchParameter;
use App\Models\Dataset;
use Illuminate\Support\Facades\Log;
use Solarium\Core\Client\Adapter\Curl;
use Symfony\Component\EventDispatcher\EventDispatcher;
use \Solarium\QueryType\Select\Query\Query;

class SolariumAdapter
{
    protected $options;

    /**
     * @var \Solarium\Core\Client\Client
     */
    protected $client;

    public function __construct($serviceName, $options)
    {
        $this->options = $options;
        $adapter = new Curl();
        $dispatcher = new EventDispatcher();

        $this->client = new \Solarium\Client($adapter, $dispatcher, $options);
        // $this->client = new \Solarium\Client($options);

        // ensure service is basically available
        $ping = $this->client->createPing();
        $this->execute($ping, 'failed pinging service ' . $serviceName);
    }

    /**
     * Maps name of field returned by search engine into name of asset to use
     * on storing field's value in context of related match.
     *
     * This mapping relies on runtime configuration. Mapping is defined per
     * service in
     *
     * @param string $fieldName
     * @return string
     */
    protected function mapResultFieldToAsset($fieldName)
    {
        //if ( $this->options->fieldToAsset instanceof Zend_Config )
        //{
        //    return $this->options->fieldToAsset->get( $fieldName, $fieldName );
        //}
        return $fieldName;
    }

    public function getDomain()
    {
        return 'solr';
    }

    public function addDatasetsToIndex($datasets)
    {
        $datasets = $this->normalizeDocuments($datasets);
        $builder = new SolariumDocument($this->options);

        $slices = array_chunk($datasets, 16);
        // update documents of every chunk in a separate request
        foreach ($slices as $slice) {
            // get an update query instance
            $update = $this->client->createUpdate();

            $updateDocs = array_map(function ($rdrDoc) use ($builder, $update) {
                $solarium_document = $update->createDocument();
                return $builder->toSolrUpdateDocument($rdrDoc, $solarium_document);
            }, $slice);

            // add the documents and a commit command to the update query
            $update->addDocuments($updateDocs);
            $update->addCommit();

            // this executes the query and returns the result
            $result = $this->client->update($update);
            echo '<b>Update query executed</b><br/>';
            echo 'Query status: ' . $result->getStatus() . '<br/>';
            echo 'Query time: ' . $result->getQueryTime();

            //$this->execute($update, 'failed updating slice of documents');
        }

        // finally commit all updates
        // $update = $this->client->createUpdate();

        // $update->addCommit();

        // $this->execute($update, 'failed committing update of documents');

        return $this;
    }

    protected function normalizeDocuments($documents)
    {
        if (!is_array($documents)) {
            $documents = array($documents);
        }

        foreach ($documents as $document) {
            if (!($document instanceof Dataset)) {
                throw new \InvalidArgumentException("invalid dataset in provided set");
            }
        }

        return $documents;
    }

    public function createQuery(): SearchParameter
    {
        return new SearchParameter();
    }

    public function customSearch(SearchParameter $queryParameter)
    {
        $search = $this->client->createSelect();
        $solariumQuery = $this->applyParametersToSolariumQuery($search, $queryParameter, false);
        $searchResult = $this->processQuery($solariumQuery);
        return $searchResult;
    }

    protected function applyParametersToSolariumQuery(Query $query, SearchParameter $parameters, $preferOriginalQuery)
    {
        if ($parameters) {
            //$subfilters = $parameters->getSubFilters();
            //if ( $subfilters !== null ) {
            //    foreach ( $subfilters as $name => $subfilter ) {
            // if ( $subfilter instanceof Opus_Search_Solr_Filter_Raw
            //|| $subfilter instanceof Opus_Search_Solr_Solarium_Filter_Complex ) {
            //            $query->createFilterQuery( $name )
            //                  ->setQuery( $subfilter->compile( $query ) );
            //        }
            //    }
            //}

            // $filter = $parameters->getFilter();//"aa"
            // if ( $filter instanceof Opus_Search_Solr_Filter_Raw
            // || $filter instanceof Opus_Search_Solr_Solarium_Filter_Complex ) {
            // if ( !$query->getQuery() || !$preferOriginalQuery ) {
            // $compiled = $filter->compile( $query );
            // if ( $compiled !== null ) {
            // // compile() hasn't implicitly assigned query before
            // $query->setQuery( $compiled );
            // }
            // }
            // }

            $filter = $parameters->getFilter(); //"aa"  all: '*:*'
            if ($filter !== null) {
                //$query->setStart( intval( $start ) );
                //$query->setQuery('%P1%', array($filter));
                $query->setQuery($filter);
            }

            $start = $parameters->getStart();
            if ($start !== null) {
                $query->setStart(intval($start));
            }

            $rows = $parameters->getRows();
            if ($rows !== null) {
                $query->setRows(intval($rows));
            }

            $union = $parameters->getUnion();
            if ($union !== null) {
                $query->setQueryDefaultOperator($union ? 'OR' : 'AND');
            }

            $fields = $parameters->getFields();
            if ($fields !== null) {
                $query->setFields($fields);
            }

            $sortings = $parameters->getSort();
            if ($sortings !== null) {
                $query->setSorts($sortings);
            }

            $facet = $parameters->getFacet();
            if ($facet !== null) {
                $facetSet = $query->getFacetSet();
                foreach ($facet->getFields() as $field) {
                    $facetSet->createFacetField($field->getName())
                        ->setField($field->getName())
                        ->setMinCount($field->getMinCount())
                        ->setLimit($field->getLimit())
                        ->setSort($field->getSort() ? 'index' : null);
                }
                if ($facet->isFacetOnly()) {
                    $query->setFields(array());
                }
            }
        }
        return $query;
    }

    protected function execute($query, $actionText)
    {
        $result = null;
        try {
            $result = $this->client->execute($query);
        } catch (\Solarium\Exception\HttpException$e) {
            sprintf('%s: %d %s', $actionText, $e->getCode(), $e->getStatusMessage());
        } finally {
            return $result;
        }

        // if ( $result->getStatus() ) {
        // throw new Opus_Search_Exception( $actionText, $result->getStatus() );
        // }
    }

    protected function processQuery(\Solarium\QueryType\Select\Query\Query$query): SearchResult
    {
        // send search query to service
        $request = $this->execute($query, 'failed querying search engine');

        //$count = $request->getDocuments();
        // create result descriptor
        $result = SearchResult::create()
            ->setAllMatchesCount($request->getNumFound())
            ->setQueryTime($request->getQueryTime());

        // add description on every returned match
        $excluded = 0;
        foreach ($request->getDocuments() as $document) {
            /** @var \Solarium\QueryType\Select\Result\Document $document */
            $fields = $document->getFields();

            if (array_key_exists('id', $fields)) {
                $match = $result->addMatch($fields['id']);

                foreach ($fields as $fieldName => $fieldValue) {
                    switch ($fieldName) {
                        case 'id':
                            break;

                        case 'score':
                            $match->setScore($fieldValue);
                            break;

                        case 'server_date_modified':
                            $match->setServerDateModified($fieldValue);
                            break;

                        case 'fulltext_id_success':
                            $match->setFulltextIDsSuccess($fieldValue);
                            break;

                        case 'fulltext_id_failure':
                            $match->setFulltextIDsFailure($fieldValue);
                            break;

                        default:
                            $match->setAsset($fieldName, $fieldValue);
                            //$match->setAsset( $this->mapResultFieldToAsset( $fieldName ), $fieldValue );
                            break;
                    }
                }
            } else {
                $excluded++;
            }
        }

        if ($excluded > 0) {
            Log::warning(sprintf(
                'search yielded %d matches not available in result set for missing ID of related document',
                $excluded
            ));
        }

        return $result;
    }
}