<?php

namespace FiloBlu\Refilo\Remote\Connector;

use Elasticsearch\Client;
use Elasticsearch\ClientBuilder;

/**
 * Class ElasticSearchConnector
 * @package FiloBlu\Refilo\Remote\Connector
 */
class ElasticSearch extends AbstractConnector
{
    /**
     * @var Client
     */
    protected $handler;

    /**
     * @return mixed|void
     */
    public function disconnect(): ConnectorInterface
    {
        if (!$this->handler) {
            return $this;
        }

        $this->handler = null;

        return $this;
    }

    /**
     * @see https://www.elastic.co/guide/en/elasticsearch/client/php-api/6.x/indexing_documents.html
     *
     * @param iterable $entities
     * @param string $collection
     * @return mixed|void
     */
    public function create(iterable $entities, string $collection)
    {
        $this->logger->info("start reindex for collection " . $collection);
        $this->createIndex($collection);

        $i = 0;
        $params = ['body' => []];

        foreach ($entities as $entity) {

            $params['body'][] = [
                'update' => [
                    '_index' => $collection,
                    '_type'  => '_doc',
                    '_id'    => $entity->getId(),
                ]
            ];

            $params['body'][] = [
                'doc' => $entity->toArray(),
                'detect_noop' => false,
                'doc_as_upsert' => true
            ];

            if (($i++ % 1000) === 0) {
                $this->logger->info("Bulk Op Start $i");
                $this->handler->bulk($params);
                $this->logger->info("Bulk Op End $i");
                $params = ['body' => []];
            }
        }

        if (!empty($params['body'])) {
            $this->logger->info("Last Bulk Op Start");
            $this->handler->bulk($params);
            $this->logger->info("Last Bulk Op End");
        }
        $this->logger->info("end reindex for collection " . $collection);
    }

    /**
     * @param $index
     * @return $this
     */
    public function createIndex($index)
    {
        $client = $this->getHandler();
        $version = $this->getVersion();

        $indices = $client->indices()->get(['index' => $index, 'ignore_unavailable' => true]);

        if (array_key_exists($index, $indices)) {
            return $this;
        }

        $indexes['autosuggests']['type'] = 'completion';
        $indexes['name']['type'] = 'text';
        $indexes['name']['copy_to'] = 'autosuggests';
        $indexes['name']['fields']['keyword']['type'] = 'keyword';
        $indexes['sku']['type'] = 'keyword';
        $indexes['id']['type'] = 'long';

        $mappingDefinition = [
            'enabled'           => true,
            '_source'           => ['enabled' => true],
            'dynamic_templates' => [
                [
                    'attributes' => [
                        'match_mapping_type' => 'string',
                        'path_match'         => 'attributes.*',
                        'mapping'            => [
                            'type'         => 'keyword',
                            'ignore_above' => 256
                        ]
                    ],
                ],
                [
                    'filters' => [
                        'match_mapping_type' => 'string',
                        'path_match'         => 'filters.*',
                        'mapping'            => [
                            'type'         => 'keyword',
                            'ignore_above' => 256
                        ]
                    ],
                ],
                [
                    'sortables' => [
                        'match_mapping_type' => 'string',
                        'path_match'         => 'sortables.*',
                        'mapping'            => [
                            'type'         => 'keyword',
                            'ignore_above' => 256
                        ]
                    ],
                ]
            ],
            'properties'        => $indexes
        ];

        $mapping = $mappingDefinition;

        if (version_compare($version, '7.0.0', '<')) {
            $mapping = ['_doc' => $mappingDefinition];
        }

        $params = [
            'index' => $index,
            'body'  => [
                'settings' => [
                    'index.priority'                      => '1',
                    'index.query.default_field'           => ['*'],
                    'index.refresh_interval'              => '60s',
                    'index.write.wait_for_active_shards'  => '1',
                    'index.mapping.total_fields.limit'    => '1000',
                    'index.blocks.read_only_allow_delete' => 'false',
                    'index.number_of_replicas'            => '0'
                ],

                'mappings' => $mapping
            ]
        ];

        $client->indices()->create($params);

        return $this;
    }

    protected function getHandler()
    {
        $this->connect();

        return $this->handler;
    }

    /**
     * @return ConnectorInterface
     */
    public function connect(): ConnectorInterface
    {
        if (!$this->handler) {
            $this->handler = ClientBuilder::create()
                ->setRetries(2)
                ->setHosts($this->getHosts())
                ->build();
        }

        return $this;
    }

    /**
     * @return array
     */
    protected function getHosts(): array
    {
        $hosts = $this->getConfiguration()->getData(self::CONFIGURATION_HOST_POOL);

        $dsns = [];
        foreach ($hosts as $host) {
            $dsn = "{$host['scheme']}://";

            if ($host['username']) {
                $dsn .= "{$host['username']}:{$host['password']}@";
            }

            $dsn .= $host['host'];

            if ($host['port']) {
                $dsn .= ":{$host['port']}";
            }
            $dsns[] = $dsn;
        }

        return $dsns;
    }

    /**
     * @return string|null
     */
    public function getVersion()
    {
        return $this->handler->info()['version']['number'];
    }

    /**
     * @param array $entities
     * @param string $collection
     * @return void
     */
    public function delete(iterable $entities, string $collection)
    {
    }

    /**
     * @param iterable $entities
     * @param string $collection
     * @return void
     */
    public function update(iterable $entities, string $collection)
    {
        $this->createIndex($collection);
        $i = 0;
        $params = ['body' => []];

        foreach ($entities as $entity) {
            $params['body'][] = [
                'update' => [
                    '_index' => $collection,
                    '_type'  => '_doc',
                    '_id'    => $entity->getId()
                ]
            ];

            $params['body'][] = [
                'doc' => $entity->toArray(),
                'detect_noop' => false
            ];

            if (($i++ % 1000) === 0) {
                $this->handler->bulk($params);
                $params = ['body' => []];
            }
        }

        if (!empty($params['body'])) {
            $this->handler->bulk($params);
        }
    }

    /**
     * @return array|mixed|null
     */
    protected function getPrefix()
    {
        return $this->getConfiguration()->getData('prefix');
    }
}
