<?php
declare(strict_types=1);

namespace FCA\StockApi\Collection;

use FCA\StockApi\Api;
use FCA\StockApi\Collection\Filter\Builder\Validator;
use FCA\StockApi\Document\DistinctValue;
use FCA\StockApi\Exception\ApiException;

class Offer
{
    /**
     * @var \MongoDB\Collection
     */
    protected $offerCollection;

    /**
     * Offer constructor.
     * @param Api $api
     * @throws ApiException
     */
    public function __construct(Api $api)
    {
        if ($api->getCollection() === null) {
            throw new ApiException('Database collection is missing!');
        }

        $this->offerCollection = $api->getCollection();
    }

    /**
     * @param array $filters
     * @param array|null $sort
     * @param int|null $limit
     * @param int|null $skip
     * @return \FCA\StockApi\Document\Offer[]
     * @throws ApiException
     */
    public function find(array $filters = [], ?array $sort = null, ?int $limit = null, ?int $skip = null): array
    {
        Validator::validateFilter($filters);

        $options = [];
        if ($sort != [] and $sort !== null) {
            if (!Validator::isValidSortArray($sort)) {
                throw new ApiException('Sort array is invalid!');
            }

            $options['collation'] = ['locale' => 'pl'];
            $options['sort'] = $sort;
        }

        if ($limit !== null) {
            if (is_int($limit)) {
                $options['limit'] = $limit;
            } else {
                throw new ApiException('Limit must be integer!');
            }
        }

        if ($skip !== null) {
            if (is_int($skip)) {
                $options['skip'] = $skip;
            } else {
                throw new ApiException('Skip must be integer!');
            }
        }

        if ($sort === ['random' => true]) {
            if ($limit === null) {
                throw new ApiException('Limit must be defined in random find!');
            }
            $docs = $this->offerCollection->aggregate(
                [
                    [
                        '$match' => (object) $filters
                    ],
                    [
                        '$sample' => [
                            'size' => $limit
                        ]
                    ],
                ]
            );
        } else {
            $docs = $this->offerCollection->find($filters, $options);
        }
        $ret = [];

        foreach ($docs as $doc) {
            $ret[] = new \FCA\StockApi\Document\Offer((array)$doc);
        }

        $docs = null;
        unset($docs);

        return $ret;
    }

    public function findOne(array $filters = [], ?array $sort = null, ?int $skip = null): ?\FCA\StockApi\Document\Offer
    {
        Validator::validateFilter($filters);

        $options = [];
        if ($sort != [] and $sort !== null) {
            if (!Validator::isValidSortArray($sort)) {
                throw new ApiException('Sort array is invalid!');
            }

            $options['collation'] = ['locale' => 'pl'];
            $options['sort'] = $sort;
        }

        if ($skip !== null) {
            if (is_int($skip)) {
                $options['skip'] = $skip;
            } else {
                throw new ApiException('Skip must be integer!');
            }
        }

        if ($sort === ['random' => true]) {
            $doc = $this->offerCollection->aggregate(
                [
                    [
                        '$match' => (object) $filters
                    ],
                    [
                        '$sample' => [
                            'size' => 1
                        ]
                    ],
                ]
            );

            if (isset($doc[0])) {
                $doc = $doc[0];
            }
        } else {
            $doc = $this->offerCollection->findOne($filters, $options);
        }

        if (!$doc) {
            return null;
        }

        $offer = new \FCA\StockApi\Document\Offer((array)$doc);

        $doc = null;
        unset($doc);

        return $offer;
    }

    public function findOneById(int $id): ?\FCA\StockApi\Document\Offer
    {
        $doc = $this->offerCollection->findOne(['vehicle_id' => $id]);

        if (!$doc) {
            return null;
        }

        $offer = new \FCA\StockApi\Document\Offer((array)$doc);

        $doc = null;
        unset($doc);

        return $offer;
    }

    /**
     * @return DistinctValue[]
     */
    public function findDistinct(string $field, array $filters = []): array
    {
        Validator::validateFilter($filters);

        $differentValues = $this->offerCollection->aggregate(
            [
                [
                    '$match' => (object) $filters
                ],
                [
                    '$unwind' => '$' . $field
                ],
                [
                    '$group' => [
                        '_id' => '$' . $field,
                        'count' => [
                            '$sum' => 1
                        ]
                    ]
                ]
            ]
        );

        /**
         * @var $distinctValues DistinctValue[]
         */
        $distinctValues = [];

        foreach ($differentValues as $doc) {
            $distinctValues[] = new DistinctValue($doc['_id'], $doc['count']);
        }

        return $distinctValues;
    }

    public function count(array $filters = []): int
    {
        Validator::validateFilter($filters);

        return $this->offerCollection->countDocuments($filters);
    }
}
