<?php

namespace FCA\StockApi\Tests\Unit\Collection;

use FCA\StockApi\Api;
use FCA\StockApi\Collection\Filter\Builder;
use FCA\StockApi\Collection\Filter\Builder\Fields;
use FCA\StockApi\Collection\Offer;
use FCA\StockApi\Collection\Sort\Sort;
use FCA\StockApi\Exception\ApiException;
use \PHPUnit\Framework\TestCase;

final class OfferCollectionTest extends TestCase
{
    /**
     * @var \FCA\StockApi\Collection\Offer|null
     */
    private $offerCollection;

    /**
     * Metoda odpowiedzialna za utworzenie obiektu \FCA\StockApi\Collection\Offer
     * @throws \FCA\StockApi\Exception\ApiException
     */
    public function setUp(): void
    {
        $client = new \MongoDB\Client('mongodb://db-stock:27017');
        $db = $client->selectDatabase('stock-www');

        $api = new Api();
        $api->setDatabase($db);
        $api->setCollection('offers');

        $this->offerCollection = new Offer($api);
    }

    /**
     * Przyjeta przez metodę nieprawidłowa tablica filtrów powinna zwrócić pustą tablicę
     * @throws ApiException
     */
    public function testFindShouldReturnEmptyArray()
    {
        $this->expectException(ApiException::class);
        $this->offerCollection->find(['bad-array']);
    }

    /**
     * Metoda find wywołana bez argumentów powinna zwrócić tablicę wszystkich dokumentów
     * @throws ApiException
     */
    public function testFindShouldReturnAllDocuments()
    {
        $offers = $this->offerCollection->find();

        $this->assertInternalType('array', $offers);
        $this->assertInstanceOf(\FCA\StockApi\Document\Offer::class, $offers[0]);
        $this->assertSame(2815, count($offers));

        unset($offers);
    }

    /**
     * Metoda find wywołana z argumentem limit powinna zwrócić ograniczoną listę wyników
     * @throws ApiException
     */
    public function testFindShouldReturnFirstDocument()
    {
        $offers = $this->offerCollection->find([], null, 1);

        $this->assertInternalType('array', $offers);
        $this->assertSame(1, count($offers));
        $this->assertInstanceOf(\FCA\StockApi\Document\Offer::class, $offers[0]);

        $this->assertEquals($this->offerCollection->findOne(), $offers[0]);

        unset($offers);
    }

    /**
     * @depends testFindShouldReturnFirstDocument
     * Metoda find wywołana z argumentem limit oraz skip powinna zwrócić ograniczoną listę wyników
     * @throws ApiException
     */
    public function testFindShouldReturnSecondDocument()
    {
        $offers = $this->offerCollection->find([], null, 1, 1);

        $this->assertInternalType('array', $offers);
        $this->assertSame(1, count($offers));
        $this->assertInstanceOf(\FCA\StockApi\Document\Offer::class, $offers[0]);

        $this->assertEquals($this->offerCollection->findOne([], null, 1), $offers[0]);

        unset($offers);
    }

    /**
     * @depends testFindShouldReturnSecondDocument
     * Metoda find wywołana z obiektem sortującym powinna zwrócić posortowane wyniki
     * @throws ApiException
     */
    public function testFindShouldReturnSortedDocuments()
    {
        $sort = new Sort();
        $sort->addSortBy(Fields::FINAL_PRICE_NETTO, Sort::ASC);
        $sort->addSortBy(Fields::ID, Sort::ASC);
        $offers = $this->offerCollection->find([], $sort->getSort(), 10);

        $this->assertInternalType('array', $offers);
        $this->assertSame(10, count($offers));
        $this->assertInstanceOf(\FCA\StockApi\Document\Offer::class, $offers[0]);

        $previous = null;
        $sorted = true;
        foreach ($offers as $offer) {
            if ($previous === null) {
                $previous = $offer->getPrice()->getFinal()->getNetto();
            } else {
                if ($previous > $offer->getPrice()->getFinal()->getNetto()) {
                    $sorted = false;
                    break;
                }
            }
        }

        $this->assertTrue($sorted);

        unset($offers);
    }

    /**
     * Metoda find wywołana z prawidłową tablicą filtrów powinna zwrócić prawidłową tablicę
     * @throws ApiException
     */
    public function testFindShouldReturnFilteredDocuments()
    {
        $offers = $this->offerCollection->find(['$and' => [["model.code" => "357"], ["color.code" => "722"]]]);

        $allColorsIsValid = true;
        $allModelsIsValid = true;
        foreach ($offers as $offer) {
            if ($offer->getColor()->getCode() !== "722") {
                $allColorsIsValid = false;
            }
            if ($offer->getModel()->getCode() !== "357") {
                $allModelsIsValid = false;
            }
        }

        $this->assertTrue($allColorsIsValid);
        $this->assertTrue($allModelsIsValid);
        $this->assertSame(126, count($offers));
        unset($offers);
    }

    /**
     * Metoda findOne wywolana bez argumentów powinna zwrócić pierwszy (jeden) dokument
     * @throws ApiException
     */
    public function testFindOneShouldReturnFirstDocument()
    {
        $offer = $this->offerCollection->findOne();

        $this->assertInstanceOf(\FCA\StockApi\Document\Offer::class, $offer);
        unset($offer);
    }

    /**
     * Metoda findOne wywołana z nieprawidłową tablicą filtrów powinna zwrócić null
     * @throws ApiException
     */
    public function testFindOneShouldThrowException()
    {
        $this->expectException(ApiException::class);
        $this->offerCollection->findOne(['bad' => 'filter']);
    }

    /**
     * Metoda findOne wywołana z nieprawidłową tablicą filtrów powinna zwrócić null
     * @throws ApiException
     */
    public function testFindOneShouldReturnNull()
    {
        $offer = $this->offerCollection->findOne((new Builder())->is(Fields::ID, '<', 1));

        $this->assertNull($offer);
    }

    /**
     * Metoda findOneById wywolana z prawidłowym argumentem powinna zwrócić prawidłowy dokument
     * @throws ApiException
     */
    public function testFindByIdShouldReturnOneDocument()
    {
        $id = 94517;
        $offer = $this->offerCollection->findOneById($id);

        $this->assertInstanceOf(\FCA\StockApi\Document\Offer::class, $offer);
        $this->assertSame($id, $offer->getId());
        unset($offer);
    }

    /**
     * Metoda findOneById wywolana z nie prawidłowym argumentem powinna zwrócić null
     * @throws ApiException
     */
    public function testFindByIdShouldReturnNull()
    {
        $id = -10;
        $offer = $this->offerCollection->findOneById($id);

        $this->assertNull($offer);
    }

    public function testFindDistinct()
    {
        $distinct = $this->offerCollection->findDistinct(Fields::BRAND_CODE);

        $this->assertInternalType('array', $distinct);
        $this->assertCount(5, $distinct);

        $distinct = $this->offerCollection->findDistinct(Fields::BRAND_CODE, [Fields::BRAND_CODE => '00']);

        $this->assertInternalType('array', $distinct);
        $this->assertCount(1, $distinct);
        $this->assertEquals('00', $distinct[0]->getValue());
        $this->assertEquals(2141, $distinct[0]->getCount());
    }

    public function testCount()
    {
        $this->assertEquals(2815, $this->offerCollection->count());
        $this->assertEquals(2141, $this->offerCollection->count([Fields::BRAND_CODE => '00']));
    }
}
