<?php
declare(strict_types=1);

namespace FiloBlu\Refilo\Remote\Connector;

use MongoDB\Collection;
use MongoDB\Driver\BulkWrite;
use MongoDB\Driver\Command;
use MongoDB\Driver\Exception\Exception;
use MongoDB\Driver\Manager;

/**
 * Class MongoDB
 * @package FiloBlu\Refilo\Remote\Connector
 */
class MongoDb extends AbstractConnector
{
    /**
     * @var Manager
     */
    protected $connection = null;

    /**
     * @var string
     */
    protected $database;

    /**
     * @return mixed|void
     */
    public function disconnect(): ConnectorInterface
    {
        $this->connection = null;
        return $this;
    }

    /**
     * @param iterable $entities
     * @param string $collection
     * @return void
     * @throws Exception
     */
    public function create(iterable $entities, string $collection)
    {
        if (empty($entities)) {
            return;
        }

        $this->connect();

        if (version_compare($this->getVersion(), '4.2.0', '>')) {
            $mongoCollection = new Collection($this->connection, $this->getDatabase(), $collection);
            $mongoCollection->createIndex(['$**' => 1]);
        }

        $bulk = new BulkWrite();

        foreach ($entities as $entity) {
            $entity->setData('_id', $entity->getId());
            $bulk->update(
                ['_id' => $entity->getId()],
                $entity->toArray(),
                ['multi' => false, 'upsert' => true]
            );
        }

        $this->connection->executeBulkWrite("{$this->getDatabase()}.{$collection}", $bulk);
    }

    /**
     * @return ConnectorInterface
     */
    public function connect(): ConnectorInterface
    {
        if ($this->connection === null) {
            $this->connection = new Manager($this->getUri());
        }

        return $this;
    }

    /**
     * https://tools.ietf.org/html/rfc3986
     * scheme://[username:password@]host1[:port1][,host2[:port2],...[,hostN[:portN]]][/[database][?options]]
     */
    protected function getUri(): string
    {
        $hosts = $this->getConfiguration()->getData(self::CONFIGURATION_HOST_POOL);

        $host = array_shift($hosts);

        $dsn = "{$host['scheme']}://";

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

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

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

        $extraHosts = [];
        foreach ($hosts as $host) {
            $extraHosts[] = $host['host'] . ($host['port'] ? ":{$host['port']}" : '');
        }

        $dsn .= implode(',', $extraHosts);

        if (isset($host['path'])) {
            $this->database = $host['path'];
        }

        return $dsn;
    }

    /**
     * @return string
     * @throws Exception
     */
    public function getVersion()
    {
        $this->connect();
        $cursor = $this->connection->executeCommand($this->database, new Command(['serverStatus' => true]));
        $infos = iterator_to_array($cursor);
        return $infos[0]->version;
    }

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

        $this->connect();
        $bulk = new BulkWrite();

        foreach ($entities as $id) {
            // force to string
            $bulk->delete(['_id' => "$id"]);
        }

        $this->connection->executeBulkWrite("{$this->getDatabase()}.{$collection}", $bulk);
    }

    /**
     * @param iterable $entities
     * @param string $collection
     * @return void
     */
    public function update(iterable $entities, string $collection)
    {
        if (empty($entities)) {
            return;
        }

        $this->connect();
        $bulk = new BulkWrite();

        foreach ($entities as $entity) {
            $bulk->update(
                ['_id' => $entity->getId()],
                ['$set' => $entity->toArray()],
                ['multi' => false, 'upsert' => true]
            );
        }

        $this->connection->executeBulkWrite("{$this->getDatabase()}.{$collection}", $bulk);
    }

    /**
     * @return string
     */
    public function getDatabase()
    {
        $database = $this->getMetadata()->getDatabase();
        if($database){
            return $database;
        }
        return $this->database;
    }
}
