<?php

namespace App\Tests\A_Unit\Mapper\TarifSpecifique;

use Myvetshop\Module\Clinique\Adapter\Factory\SpecificPriceFactory;
use Myvetshop\Module\Clinique\Adapter\Repository\GroupReductionRepository;
use Myvetshop\Module\Clinique\Adapter\Repository\SpecificPriceRepository;
use Myvetshop\Module\Clinique\Entity\MyvetshopTarifSpecifique;
use Myvetshop\Module\Clinique\Mapper\TarifSpecifique\TarifSpecifiqueToSpecificPriceMapper;
use PHPUnit\Framework\MockObject\MockObject;
use PHPUnit\Framework\TestCase;

class TarifSpecifiqueToSpecificPriceMapperTest extends TestCase
{
    /**
     * @var \Db&MockObject
     */
    private \Db $db;

    /**
     * @var GroupReductionRepository&MockObject
     */
    private GroupReductionRepository $groupReductionRepository;

    /**
     * @var SpecificPriceFactory&MockObject
     */
    private SpecificPriceFactory $specificPriceFactory;

    /**
     * @var SpecificPriceRepository&MockObject
     */
    private SpecificPriceRepository $specificPriceRepository;

    private TarifSpecifiqueToSpecificPriceMapper $mapper;

    protected function setUp(): void
    {
        $this->db = $this->createMock(\Db::class);
        $this->groupReductionRepository = $this->createMock(GroupReductionRepository::class);
        $this->specificPriceFactory = $this->getMockBuilder(SpecificPriceFactory::class)
            ->onlyMethods(['createEntity'])
            ->getMock();
        $this->specificPriceFactory
            ->method('createEntity')
            ->willReturnCallback(function () {
                return $this->createMock(\SpecificPrice::class);
            });
        $this->specificPriceRepository = $this->createMock(SpecificPriceRepository::class);

        $this->mapper = new TarifSpecifiqueToSpecificPriceMapper(
            $this->db,
            $this->groupReductionRepository,
            $this->specificPriceFactory,
            $this->specificPriceRepository,
        );
    }

    public function testBuildUpdateQueries(): void
    {
        $group = $this->createMock(\Group::class);
        $group->id = 3;

        $tarif = new MyvetshopTarifSpecifique();

        $tarif->tarifFardelage = '10.50';
        $tarif->tarifPrivilege = '10.00';
        $tarif->idProduct = 1;
        $tarif->idProductAttribute = 2;

        $this->groupReductionRepository
            ->method('getReductionForGroups')
            ->with(1, [$group])
            ->willReturn([
               3 => '0.00',
            ]);

        $queries = $this->mapper->buildUpdateQueries($tarif, [$group]);

        $this->assertCount(2, $queries['insert']);
        $this->assertEquals(1, $queries['insert'][0]->id_product);
        $this->assertEquals(2, $queries['insert'][0]->id_product_attribute);
        $this->assertEquals(3, $queries['insert'][0]->id_group);
        $this->assertEquals(1, $queries['insert'][0]->from_quantity);
        $this->assertEquals(10.00, $queries['insert'][0]->price);

        $this->assertEquals(1, $queries['insert'][1]->id_product);
        $this->assertEquals(2, $queries['insert'][1]->id_product_attribute);
        $this->assertEquals(3, $queries['insert'][1]->id_group);
        $this->assertEquals(2, $queries['insert'][1]->from_quantity);
        $this->assertEquals(10.50, $queries['insert'][1]->price);
    }

    public function testToSpecificPrices(): void
    {
        $group = $this->createMock(\Group::class);
        $group->id = 1;

        $tarif = new MyvetshopTarifSpecifique();

        $tarif->tarifFardelage = '10.50';
        $tarif->tarifPrivilege = '10.00';
        $tarif->idProduct = 1;
        $tarif->idProductAttribute = 1;

        $ret = $this->mapper->toSpecificPrices($tarif, $group, 0.1);

        $this->assertNotNull($ret['fardelage']);
        $this->assertNotNull($ret['privilege']);

        // Le montant doit prendre en compte la réduction de groupe
        $this->assertEquals(1, $ret['privilege']->from_quantity);
        $this->assertEquals(11.11, $ret['privilege']->price);
        $this->assertEquals(2, $ret['fardelage']->from_quantity);
        $this->assertEquals(11.67, $ret['fardelage']->price);
    }

    public function testExecuteUpdateQueries(): void
    {
        $specificPriceI = $this->createMock(\SpecificPrice::class);
        $specificPriceI->id_product = 10;
        $specificPriceI->id_product_attribute = 11;
        $specificPriceI->id_group = 2;
        $specificPriceI->price = '10.00';
        $specificPriceD = $this->createMock(\SpecificPrice::class);
        $specificPriceD->id = 1;
        $specificPriceU = $this->createMock(\SpecificPrice::class);
        $specificPriceU->id = 12;
        $specificPriceU->price = '11.00';

        $queries = [
            'delete' => [$specificPriceD],
            'insert' => [$specificPriceI],
            'update' => [$specificPriceU],
        ];

        $matcher = $this->exactly(3);
        $this->db
            ->expects($matcher)
            ->method('execute')
            ->willReturnCallback(function (string $query) use ($matcher) {
                switch ($matcher->getInvocationCount()) {
                    case 1:
                        $this->assertEquals('DELETE FROM `pstest_specific_price` WHERE `id_specific_price` IN (1)', $query);
                        break;

                    case 2:
                        $this->assertEquals('INSERT INTO `pstest_specific_price` (`id_specific_price_rule`, `id_cart`, `id_product`, `id_product_attribute`, `id_shop`, `id_shop_group`, `id_currency`, `id_country`, `id_customer`, `id_group`, `from_quantity`, `reduction`, `reduction_tax`, `reduction_type`, `price`, `from`, `to`) VALUES (0, 0, 10, 11, 0, 0, 0, 0, 0, 2, 0, 0, 1, "amount", 10, "0000-00-00 00:00:00", "0000-00-00 00:00:00")', $query);
                        break;

                    case 3:
                        $this->assertEquals('UPDATE `pstest_specific_price` SET price = 11 WHERE `id_specific_price` = 12', $query);
                        break;
                }
            });

        $this->mapper->executeUpdateQueries($queries);
    }

    public function testGetSortedSpecificPrices(): void
    {
        $tarif = new MyvetshopTarifSpecifique();

        $tarif->tarifFardelage = '10.50';
        $tarif->tarifPrivilege = '10.00';
        $tarif->idProduct = 1;
        $tarif->idProductAttribute = 1;

        $specificPrice1 = $this->createMock(\SpecificPrice::class);
        $specificPrice1->id_group = 1;
        $specificPrice1->from_quantity = 1;
        $specificPrice2 = $this->createMock(\SpecificPrice::class);
        $specificPrice2->id_group = 1;
        $specificPrice2->from_quantity = 2;
        $specificPrice3 = $this->createMock(\SpecificPrice::class);
        $specificPrice3->id_group = 2;
        $specificPrice3->from_quantity = 2;

        $this->specificPriceRepository
            ->method('findByTarifSpecifiqueAndGroups')
            ->with($tarif, [1, 2])
            ->willReturn([
                $specificPrice1,
                $specificPrice2,
                $specificPrice3,
            ]);

        $sortedSpecificPrices = $this->mapper->getSortedSpecificPrices($tarif, [1, 2]);

        $this->assertSame(
            [
                1 => [
                    'fardelage' => $specificPrice2,
                    'privilege' => $specificPrice1,
                ],
                2 => [
                    'fardelage' => $specificPrice3,
                    'privilege' => null,
                ],
            ],
            $sortedSpecificPrices
        );
    }

    public function testPrepareUpdateQueries(): void
    {
        $specificPrice1 = $this->createMock(\SpecificPrice::class);
        $specificPrice1->price = '10.00';
        $specificPrice2 = $this->createMock(\SpecificPrice::class);
        $specificPrice3 = $this->createMock(\SpecificPrice::class);
        $specificPrice3->price = '11.00';

        $actual = [
            'fardelage' => null,
            'privilege' => $specificPrice1,
        ];

        $needed = [
            'fardelage' => $specificPrice2,
            'privilege' => $specificPrice3,
        ];

        $queries = $this->mapper->prepareUpdateQueries($actual, $needed);

        $this->assertSame(
            [
                'delete' => [],
                'insert' => [$specificPrice2],
                'update' => [$specificPrice1],
            ],
            $queries
        );
        $this->assertEquals($specificPrice3->price, $specificPrice1->price);
    }

    public function testPrepareUpdateQueriesDelete(): void
    {
        $specificPrice1 = $this->createMock(\SpecificPrice::class);
        $specificPrice1->price = '10.00';

        $actual = [
            'fardelage' => null,
            'privilege' => $specificPrice1,
        ];

        $needed = [
            'fardelage' => null,
            'privilege' => null,
        ];

        $queries = $this->mapper->prepareUpdateQueries($actual, $needed);

        $this->assertSame(
            [
                'delete' => [$specificPrice1],
                'insert' => [],
                'update' => [],
            ],
            $queries
        );
    }
}
