<?php

use KupShop\DevelopmentBundle\Util\Tests\CartTestTrait;
use KupShop\OrderingBundle\Entity\Order\OrderItem;
use KupShop\OrderingBundle\Util\Order\OrderNumberGenerator;

class OrderBaseTest extends DatabaseTestCase
{
    use CartTestTrait;

    private OrderNumberGenerator $orderNumberGenerator;

    protected function setUp(): void
    {
        parent::setUp();

        $this->orderNumberGenerator = $this->get(OrderNumberGenerator::class);
    }

    public function testSubmitOrderNewsletter()
    {
        $this->createCart();
        $cart = $this->cart;

        $item = [
            'id_product' => 1,
            'id_variation' => 16,
            'pieces' => 1,
        ];
        $cart->addItem($item);
        $cart->setTransport(1);
        $cart->setNewsletter(true);
        $cart->createFromDB();
        $cart->invoice = [
            'email' => 'benes@wpj.cz',
            'name' => 'Ondrej',
            'surname' => 'Benes',
            'street' => null,
            'city' => null,
            'zip' => null,
            'country' => 'CZ',
            'phone' => '123456789',
        ];
        $order = $cart->submitOrder();

        $this->assertInstanceOf(Order::class, $order);
    }

    public function testFreeDelivery()
    {
        $this->createCart();
        $cart = $this->cart;

        $item = [
            'id_product' => 1,
            'id_variation' => 16,
            'pieces' => 1,
        ];
        $cart->addItem($item);
        $cart->setTransport(10);
        $cart->max_step = 1;
        $cart->createFromDB();

        $this->assertEquals('800', $cart->totalPricePay->asFloat());
    }

    public function testUpdateItemUpdatesItemCount()
    {
        $this->assertSame(1, $this->itemPieces(36));

        $order = new Order(2);
        $order->updateItem(36, 2);

        $this->assertSame(2, $this->itemPieces(36));
    }

    public function testUpdateItemDoesNotDeleteOnZeroPieces()
    {
        $this->assertSame(1, $this->itemPieces(36));

        $order = new Order(2);
        $order->updateItem(36, 0);

        $this->assertSame(0, $this->itemPieces(36));
    }

    public function testInsertItemInsertsProduct()
    {
        $originalItems = (new Order(2))->fetchItems();
        $originalItemsCount = count($originalItems);
        $this->assertEquals('-1', sqlFetchAssoc($this->sqlFind('products', 3))['in_store']);

        $order = new Order(2);
        $order->insertItem(3, null, 2, '{"test":"note"}');

        $currentItems = $order->fetchItems();
        $newItem = array_diff_key($currentItems, $originalItems);
        $newItem = current($newItem);

        $this->assertCount($originalItemsCount + 1, $currentItems);
        $this->assertEquals('3', $newItem['id_product']);
        $this->assertEquals(['test' => 'note'], $newItem['note']);
        $this->assertEquals(Decimal::fromString('1322.3140'), $newItem['total_price']['value_without_vat']);
        $this->assertEquals('-3', sqlFetchAssoc($this->sqlFind('products', 3))['in_store']);
    }

    /**
     * @expectedException \DomainException
     */
    public function testInsertItemDoesNotInsertProductWithVariants()
    {
        $originalItems = (new Order(2))->fetchItems();
        $originalItemsCount = count($originalItems);

        $order = new Order(2);
        $order->insertItem(4, null, 2, 'Test note');
    }

    public function testInsertItemInsertsVariant()
    {
        $originalItems = (new Order(2))->fetchItems();
        $originalItemsCount = count($originalItems);
        $this->assertEquals('5', sqlFetchAssoc($this->sqlFind('products_variations', 17))['in_store']);

        $order = new Order(2);
        $order->insertItem(6, 17, 2, '{"test":"note"}');

        $currentItems = $order->fetchItems();
        $newItem = array_diff_key($currentItems, $originalItems);
        $newItem = current($newItem);

        $this->assertCount($originalItemsCount + 1, $currentItems);
        $this->assertEquals('6', $newItem['id_product']);
        $this->assertEquals('17', $newItem['id_variation']);
        $this->assertEquals(['test' => 'note'], $newItem['note']);
        $this->assertEquals(Decimal::fromString('4958.6777'), $newItem['total_price']['value_without_vat']);
        $this->assertEquals('3', sqlFetchAssoc($this->sqlFind('products_variations', 17))['in_store']);
    }

    public function testCreateFromDbOrderNo()
    {
        $order = Order::createFromDbOrderNo('201300025');
        $this->assertInstanceOf('Order', $order);
        $this->assertEquals('1', $order->id);
    }

    /**
     * @dataProvider getOrdernoConfigs
     */
    public function testOrderNoCreate($pattern, $id, $expected)
    {
        global $cfg;

        $cfg['Order']['number']['pattern'] = $pattern;
        $this->assertEquals($expected, $this->orderNumberGenerator->generateOrderNumber($id));
    }

    /** @dataProvider data_testRandomOrderNumber */
    public function testRandomOrderNumber(int $orderId, string $pattern, int $length, ?string $startsWith): void
    {
        $number = $this->orderNumberGenerator->generateOrderNumber($orderId, $pattern);

        $this->assertEquals($length, strlen($number));
        $this->assertNotEmpty($number);

        if ($startsWith) {
            $this->assertStringStartsWith($startsWith, $number);
        }
    }

    public function testOrderItems(): void
    {
        $order = new Order(2);

        $this->assertNotEmpty($order->fetchItems());
        $this->assertNotEmpty($order->getItems());

        foreach ($order->fetchItems() as $item) {
            $this->assertIsArray($item, 'Using fetchItems should return historical array structure of item');
        }

        foreach ($order->getItems() as $item) {
            $this->assertInstanceOf(OrderItem::class, $item, 'Using getItems should return OrderItem[] objects');
        }
    }

    public function data_testRandomOrderNumber(): array
    {
        return [
            // test random cisla, kdyz se nespecifikuje delka
            [1, '[#RANDOM]', 6, null],
            // test random cisla s urcitou delkou
            [1, '[#RANDOM,8]', 8, null],
            [1, '[#RANDOM,4]', 4, null],
            // test random cisla s dalsima typama
            [1, '[Y,2][#RANDOM,8]', 10, date('y')],
        ];
    }

    public function getOrderNoConfigs()
    {
        $year = date('y');
        $yearFull = date('Y');
        $day = date('d');

        return [
            ['[Y,2][#ID,5]', 1337, $year.'01337'],
            ['["37"][Y][#ID,1]', 1337, '37'.$yearFull.'1337'],
            ['[D][#ID,2][#USER_ID]', 1, $day.'011'],
            ['[Y,2][#ID,2][#USER_ORDER_INDEX]', 9, $year.'091'],
            ['[Y,2][#ID-10,3]', 20, $year.'010'],
        ];
    }

    /**
     * @expectedException \InvalidArgumentException
     */
    public function testCreateFromDbOrderNoThrows()
    {
        Order::createFromDbOrderNo('nonexistent');
    }

    public function getDataSet()
    {
        return new \PHPUnit\DbUnit\DataSet\YamlDataSet(__DIR__.'/OrderBaseTest.yml');
    }

    /**
     * @param int $itemId
     *
     * @return int
     */
    private function itemPieces($itemId)
    {
        return (int) sqlFetchAssoc(sqlQuery('SELECT pieces FROM order_items WHERE id = 36'))['pieces'];
    }

    private function sqlFind($tableName, $id)
    {
        return sqlQuery("SELECT * FROM {$tableName} WHERE id = {$id}");
    }
}
