<?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 = [
            'collation' => ['locale' => 'pl'],
        ];

        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
                        ]
                    ],
                ],
                $options
            );
        } else {
            $pipeline = [
                [
                    '$match' => (object)$filters
                ]
            ];
            if (array_key_exists('isInvenduto', (array) $sort)) {
                $pipeline[] = [
                    '$addFields' => [
                        'isInvenduto' => [
                            '$cond' => [
                                'if' => '$dealer',
                                'then' => false,
                                'else' => true,
                            ]
                        ]
                    ]
                ];
            }
            if ($sort) {
                if (!Validator::isValidSortArray($sort)) {
                    throw new ApiException('Sort array is invalid!');
                }
                $pipeline[] = [
                    '$sort' => $sort
                ];
            }
            if ($skip) {
                $pipeline[] = [
                    '$skip' => $skip
                ];
            }
            if ($limit) {
                $pipeline[] = [
                    '$limit' => $limit
                ];
            }

            $docs = $this->offerCollection->aggregate(
                $pipeline,
                $options
            );
        }

        $offers = [];

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

        $docs = null;
        unset($docs);

        return $offers;
    }

    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);
    }
}
