<?php

namespace KupShop\CatalogBundle\Tests\Product;

use KupShop\CatalogBundle\Util\Product\ProductPriceWeigher;
use KupShop\StoresBundle\Utils\StoresInStore;
use Query\Operator;

class ProductWeightedPurchasePriceTest extends \DatabaseTestCase
{
    /** @var ProductPriceWeigher */
    private $productPriceWeigher;

    public function getProductIdWithInStoreSetTo($inStore): int
    {
        $IDP = 3;
        $this->updateSQL('products', ['in_store' => $inStore], ['id' => $IDP]);

        return $IDP;
    }

    protected function setUp(): void
    {
        parent::setUp();
        $this->removeModule(\Modules::STORES, null);

        $this->productPriceWeigher = $this->createPartialMock(ProductPriceWeigher::class, []);
        $this->prepareStoresItems();
    }

    public function testAddStoreItem()
    {
        $IDP = $this->getProductIdWithInStoreSetTo(0);

        $this->addStoreItem([
            'id_stock_in' => 1,
            'id_product' => $IDP,
            'id_variation' => null,
            'quantity' => 10,
            'price' => 4,
            'additional_costs' => 1,
        ]);
        $this->checkWeightedPrice($IDP, 5);

        $this->addStoreItem([
            'id_stock_in' => 1,
            'id_product' => $IDP,
            'id_variation' => null,
            'quantity' => 10,
            'price' => 6,
            'additional_costs' => 1,
        ]);
        $this->checkWeightedPrice($IDP, 6);

        $this->sellItem($IDP, 15);

        $this->addStoreItem([
            'id_stock_in' => 1,
            'id_product' => $IDP,
            'id_variation' => null,
            'quantity' => 5,
            'price' => 8,
            'additional_costs' => 1,
        ]);
        $this->checkWeightedPrice($IDP, 7.5);

        $this->sellItem($IDP, 10);
        $this->addStoreItem([
            'id_stock_in' => 1,
            'id_product' => $IDP,
            'id_variation' => null,
            'quantity' => 4,
            'price' => 7,
            'additional_costs' => 1,
        ]);

        $this->checkWeightedPrice($IDP, 8);
    }

    public function testAddStoreItemStoreInBundle()
    {
        $this->addModule(\Modules::STORES);

        $IDP = $this->getProductIdWithInStoreSetTo(0);

        $this->addStoreItem([
            'id_stock_in' => 1,
            'id_product' => $IDP,
            'id_variation' => null,
            'quantity' => 10,
            'price' => 4,
            'additional_costs' => 1,
        ]);
        $this->checkWeightedPrice($IDP, 4.5);

        $this->addStoreItem([
            'id_stock_in' => 1,
            'id_product' => $IDP,
            'id_variation' => null,
            'quantity' => 10,
            'price' => 6,
            'additional_costs' => 1,
        ]);
        $this->checkWeightedPrice($IDP, 5.333);

        $this->sellItem($IDP, 15);

        $this->addStoreItem([
            'id_stock_in' => 1,
            'id_product' => $IDP,
            'id_variation' => null,
            'quantity' => 5,
            'price' => 8,
            'additional_costs' => 1,
        ]);
        $this->checkWeightedPrice($IDP, 6.25);

        $this->sellItem($IDP, 10);
        $this->addStoreItem([
            'id_stock_in' => 1,
            'id_product' => $IDP,
            'id_variation' => null,
            'quantity' => 4,
            'price' => 7,
            'additional_costs' => 1,
        ]);

        $this->checkWeightedPrice($IDP, 6.75);
    }

    public function testEditStoreItem()
    {
        $IDP = $this->getProductIdWithInStoreSetTo(0);

        $stockInItem = $this->addStoreItem([
            'id_stock_in' => 1,
            'id_product' => $IDP,
            'id_variation' => null,
            'quantity' => 10,
            'price' => 5,
        ]);
        $this->checkWeightedPrice($IDP, 5);

        // Ponecháme stejné hodnoty - price_buy se nemění
        $this->editStoreItem($stockInItem);
        $this->checkWeightedPrice($IDP, 5);

        // Změníme cenu z 5=>6 - price_buy se změní (nejsou jiné stock itemy)
        $stockInItem['price'] = 6;
        $this->editStoreItem($stockInItem);
        $this->checkWeightedPrice($IDP, 6);

        // Přídáme zásobu 10x8 + 10x6 - price_buy = 7
        $this->addStoreItem([
            'id_stock_in' => 1,
            'id_product' => $IDP,
            'id_variation' => null,
            'quantity' => 10,
            'price' => 8,
        ]);
        $this->checkWeightedPrice($IDP, 7);

        // Aktualizujeme cenu z 6=>10. (10x8 + 10x10 - price_buy = 9)
        $stockInItem['price'] = 10;
        $this->editStoreItem($stockInItem);
        $this->checkWeightedPrice($IDP, 9);
    }

    public function testDeleteStoreItem()
    {
        $IDP = $this->getProductIdWithInStoreSetTo(0);

        $this->addStoreItem([
            'id_stock_in' => 1,
            'id_product' => $IDP,
            'id_variation' => null,
            'quantity' => 10,
            'price' => 5,
        ]);
        $this->checkWeightedPrice($IDP, 5);

        $this->addStoreItem([
            'id_stock_in' => 1,
            'id_product' => $IDP,
            'id_variation' => null,
            'quantity' => 10,
            'price' => 7,
        ]);
        $this->checkWeightedPrice($IDP, 6);

        $this->sellItem($IDP, 15);

        $last = [
            'id_stock_in' => 1,
            'id_product' => $IDP,
            'id_variation' => null,
            'quantity' => 5,
            'price' => 9,
        ];

        $this->addStoreItem($last);
        $this->checkWeightedPrice($IDP, 7.5);

        $this->deleteStoreItem($last + ['delete' => true]);
        $this->checkWeightedPrice($IDP, 6);
    }

    public function testAddStoreItemWithNegativeInStore()
    {
        $IDP = $this->getProductIdWithInStoreSetTo(-20);

        $this->addStoreItem([
            'id_stock_in' => 1,
            'id_product' => $IDP,
            'id_variation' => null,
            'quantity' => 100,
            'price' => 10,
            'additional_costs' => 0,
        ]);
        $this->checkWeightedPrice($IDP, 10);
    }

    public function testSubFromStoreItem()
    {
        $IDP = $this->getProductIdWithInStoreSetTo(70);
        $this->checkWeightedPrice($IDP, 4);

        $this->addStoreItem([
            'id_stock_in' => 1,
            'id_product' => $IDP,
            'id_variation' => null,
            'quantity' => -60,
            'price' => 0,
            'additional_costs' => 0,
        ]);
        $this->checkWeightedPrice($IDP, 4);
    }

    public function testEditOfAdditionalCostsIsIncludedInWeightedPrice()
    {
        $IDP = $this->getProductIdWithInStoreSetTo(0);
        $stockInItem = [
            'id_stock_in' => 1,
            'id_product' => $IDP,
            'id_variation' => null,
            'quantity' => 10,
            'price' => 5,
            'additional_costs' => 0,
        ];
        $stockInItem = $this->addStoreItem($stockInItem);

        $stockInItem['additional_costs'] = 5;
        $this->editStoreItem($stockInItem);

        $this->checkWeightedPrice($IDP, 10);
    }

    protected function addStoreItem($stockInItem)
    {
        $this->productPriceWeigher->updateWeightedPurchasePrice($stockInItem, 'add');
        sqlQueryBuilder()->insert('stock_in_items')->directValues($stockInItem)->execute();
        $stockInItem['id'] = sqlInsertId();

        sqlQuery('UPDATE products set in_store=in_store + :in_store where id = :id',
            ['id' => $stockInItem['id_product'], 'in_store' => $stockInItem['quantity']]);

        return $stockInItem;
    }

    protected function editStoreItem($stockInItem)
    {
        $this->productPriceWeigher->updateWeightedPurchasePrice($stockInItem, 'edit');

        if (!empty($stockInItem['id'])) {
            sqlQueryBuilder()->update('stock_in_items')->directValues($stockInItem)->andWhere(Operator::equals(['id' => $stockInItem['id']]))->execute();
        }
    }

    protected function deleteStoreItem($stockInItem)
    {
        $this->productPriceWeigher->updateWeightedPurchasePrice($stockInItem, 'delete');

        sqlQuery('UPDATE products set in_store=in_store -:in_store where id = :id',
            ['id' => $stockInItem['id_product'], 'in_store' => $stockInItem['quantity']]);
    }

    protected function sellItem($id_product, $pieces)
    {
        sqlQuery('UPDATE products set in_store=in_store - :in_store where id = :id', ['id' => $id_product, 'in_store' => $pieces]);
    }

    private function checkWeightedPrice($id_product, $weighted_price_expected)
    {
        $weighted_price = sqlQueryBuilder()->select('price_buy')->from('products')->where(Operator::equals(['id' => $id_product]))->execute()->fetchColumn();
        $this->assertEqualsWithDelta($weighted_price_expected, $weighted_price, 0.001, 'Vazena nakupni cena nesedi!');
    }

    protected function prepareStoresItems()
    {
        sqlQueryBuilder()->update('products')
            ->set('price_buy', '4')
            ->where('id = 3')->execute();

        sqlQueryBuilder()->insert('stores')
            ->directValues(['id' => 2, 'id_base_position' => 1, 'type' => StoresInStore::TYPE_EXTERNAL_STORE, 'figure' => 'Y'])
            ->execute();

        sqlQueryBuilder()->insert('stores')
            ->directValues(['id' => 3, 'id_base_position' => 1, 'type' => StoresInStore::TYPE_EXTERNAL_STORE, 'figure' => 'Y'])
            ->execute();

        sqlQueryBuilder()->insert('stores_items')
            ->directValues(['id_product' => 3, 'quantity' => 8, 'id_store' => 2])
            ->execute();

        sqlQueryBuilder()->insert('stores_items')
            ->directValues(['id_product' => 3, 'quantity' => 2, 'id_store' => 3])
            ->execute();
    }

    protected function getDataSet()
    {
        return $this->getJsonDataSetFromFile();
    }
}
