<?php

declare(strict_types=1);

namespace App\Tests\B_Functionnal\controllers\front;

use App\Tests\B_Functionnal\AbstractFrontControllerTestCase;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\ORM\EntityManagerInterface;
use Myvetshop\Module\Clinique\Adapter\Recommandation\RecommandationCartRuleRepository;
use Myvetshop\Module\Clinique\Api\Crokit\CrokitApiClient;
use Myvetshop\Module\Clinique\Api\Crokit\Dto\CrokitRecommandation;
use Myvetshop\Module\Clinique\Entity\Animal;
use Myvetshop\Module\Clinique\Entity\Recommandation;
use Myvetshop\Module\Clinique\Entity\RecommandationProduct;
use Myvetshop\Module\Clinique\Presenter\RecommandationProductPresenter;
use Myvetshop\Module\Clinique\Repository\AnimalRepository;
use Myvetshop\Module\Clinique\Repository\RecommandationRepository;
use PHPUnit\Framework\MockObject\MockObject;
use PrestaShop\PrestaShop\Adapter\Presenter\Cart\CartPresenter;

class MyRecoTest extends AbstractFrontControllerTestCase
{
    /**
     * @var \MyvetshopcliniqueMyRecoModuleFrontController&MockObject
     */
    private \MyvetshopcliniqueMyRecoModuleFrontController $controller;

    private CrokitRecommandation $crokitRecommandation;

    /**
     * @var RecommandationCartRuleRepository&MockObject
     */
    private RecommandationCartRuleRepository $recommandationCartRuleRepository;

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

        $this->controller = $this->getMockBuilder(
            \MyvetshopcliniqueMyRecoModuleFrontController::class,
        )
            ->setMethods([
                'buildFrontEndObject',
                'getBreadcrumbLinks',
                'getContainer',
                'getTemplateFile',
                'getTemplateVarConfiguration',
                'getTemplateVarCustomer',
                'getTemplateVarShop',
                'getTemplateVarUrls',
                'redirectWithNotifications',
            ])
            ->getMock();
        $this->controller
            ->method('getBreadcrumbLinks')
            ->willReturn(['links' => []]);
        $this->controller
            ->method('getContainer')
            ->willReturn(self::$kernel->getContainer());
        $this->controller
            ->cart_presenter = $this->createMock(CartPresenter::class);
        $this->controller
            ->method('getTemplateVarConfiguration')
            ->willReturn([]);
        $this->controller
            ->method('getTemplateVarCustomer')
            ->willReturnCallback([$this, 'getTemplateVarCustomer']);

        $this->controller
            ->method('getTemplateVarShop')
            ->willReturn([]);
        $this->controller
            ->method('getTemplateVarUrls')
            ->willReturn([]);
        $this->controller
            ->method('redirectWithNotifications')
            ->willReturnCallback(function ($url) {
                throw new \Exception('Redirect : ' . $url);
            });
        $this->controller
            ->method('getTemplateFile')
            ->willReturnCallback([$this, 'getTemplateFile']);
        $this->controller->page_name = 'myreco';

        $this->context->smarty
            ->assign('page', ['page_name' => 'myreco']);

        // Mock service
        $this->recommandationCartRuleRepository = $this->createMock(RecommandationCartRuleRepository::class);
        self::$kernel->getContainer()->set(RecommandationCartRuleRepository::class, $this->recommandationCartRuleRepository);

        $this->crokitRecommandation = new CrokitRecommandation();
        $this->crokitRecommandation->codeReco = 'OQU8A';
        $this->crokitRecommandation->code = 'AAA111';
        $this->crokitRecommandation->esp = 'chien';
        $this->crokitRecommandation->petname = 'kiki';
        $this->crokitRecommandation->date = new \DateTimeImmutable();
        $this->crokitRecommandation->race = 'Border collier';
        $this->crokitRecommandation->sex = 'mâle castré';
        $this->crokitRecommandation->act = 'Calme';
        $this->crokitRecommandation->dob = '25-10-2020';
        $this->crokitRecommandation->stadePhysio = 'adulte';
        $this->crokitRecommandation->patho = '';
        $this->crokitRecommandation->actualw = 16;
        $this->crokitRecommandation->idealw = 15;
        $this->crokitRecommandation->be = 571;
        $this->crokitRecommandation->nbrAliment = 1;
        $this->crokitRecommandation->productIDmvs1 = 10638;
        $this->crokitRecommandation->productIDmvs2 = null;
        $this->crokitRecommandation->productIDmvs3 = null;
        $this->crokitRecommandation->qteAliment1 = 145;
        $this->crokitRecommandation->qteAliment2 = null;
        $this->crokitRecommandation->qteAliment3 = null;
        $this->crokitRecommandation->transition12 = null;
        $this->crokitRecommandation->transition23 = null;
    }

    /**
     * @param string $template
     */
    public function getTemplateFile($template): string
    {
        return $template;
    }

    public function testNotConnectedNoCode(): void
    {
        \assert($this->context->customer instanceof MockObject);

        $this->context->customer
            ->method('isLogged')
            ->willReturn(false);

        $this->controller->initContent();

        \ob_start();
        $this->controller->display();
        $html = \ob_get_clean() ?: '';

        $this->assertStringContainsString('Recommandation Crokit', $html);
    }

    public function testNotConnectedInvalidCode(): void
    {
        \assert($this->context->customer instanceof MockObject);

        $this->context->customer
            ->method('isLogged')
            ->willReturn(false);

        $crokitApi = $this->createMock(CrokitApiClient::class);
        $crokitApi
            ->expects($this->once())
            ->method('getRecommandation')
            ->with('OQU8A')
            ->willReturn(null);

        // Remplace l'API Crokit par un mock
        self::$kernel->getContainer()->set(CrokitApiClient::class, $crokitApi);

        $_GET['code'] = 'OQU8A';
        $this->controller->initContent();

        \ob_start();
        $this->controller->display();
        $html = \ob_get_clean() ?: '';

        // Affiche le formulaire
        $this->assertStringContainsString('Recommandation Crokit', $html);
        // TODO : Tester que le message d'erreur est bien affiché quelque part !
    }

    public function testNotConnectedValidCode(): void
    {
        \assert($this->context->customer instanceof MockObject);
        $this->context->customer
            ->method('isLogged')
            ->willReturn(false);

        \assert($this->context->link instanceof MockObject);
        $this->context->link
            ->method('getModuleLink')
            ->willReturn('https://myvetshop.viw.ovh/reco?code=OQU8A');

        $this->context->link
            ->method('getImageLink')
            ->willReturn('https://myvetshop.viw.ovh/img/test.jpg');

        // Remplace l'API Crokit par un mock
        $crokitApi = $this->createMock(CrokitApiClient::class);
        $crokitApi
            ->expects($this->once())
            ->method('getRecommandation')
            ->with('OQU8A')
            ->willReturn($this->crokitRecommandation);

        self::$kernel->getContainer()->set(CrokitApiClient::class, $crokitApi);

        // Remplace le RecommandationRepository par un mock
        $recommandationRepository = $this->createMock(RecommandationRepository::class);
        self::$kernel->getContainer()->set(RecommandationRepository::class, $recommandationRepository);

        $recommandationProductPresenter = $this->getMockBuilder(RecommandationProductPresenter::class)
            ->setConstructorArgs([self::$kernel->getContainer()->get('prestashop.adapter.legacy.context')])
            ->onlyMethods(['presentDto'])
            ->getMock();

        $recommandationProductPresenter
            ->expects($this->once())
            ->method('presentDto')
            ->willReturn([
                'id' => 0,
                'months' => null,
                'quantity' => 127,
                'product' => $this->createMock(\Product::class),
                'image_id' => 10,
            ]);

        self::$kernel->getContainer()->set(RecommandationProductPresenter::class, $recommandationProductPresenter);

        $_GET['code'] = 'OQU8A';
        $this->controller->initContent();

        \ob_start();
        $this->controller->display();
        $html = \ob_get_clean() ?: '';

        $this->assertStringContainsString('Recommandation Crokit pour', $html);
        $this->assertStringContainsString($this->crokitRecommandation->esp, $html);
        $this->assertStringContainsString($this->crokitRecommandation->petname, $html);
        $this->assertStringContainsString($this->crokitRecommandation->codeReco, $html);
    }

    public function testConnectedNoCodeNoReco(): void
    {
        \assert($this->context->customer instanceof MockObject);

        $this->context->customer->id = 1;
        $this->context->customer
            ->method('isLogged')
            ->willReturn(true);

        $this->controller->initContent();

        \ob_start();
        $this->controller->display();
        $html = \ob_get_clean() ?: '';

        $this->assertStringContainsString('Aucune recommandation enregistrée', $html);
    }

    public function testConnectedNoCodeWithReco(): void
    {
        \assert($this->context->customer instanceof MockObject);

        $this->context->customer->id = 1;
        $this->context->customer
            ->method('isLogged')
            ->willReturn(true);

        // Remplace le RecommandationRepository par un mock
        $recommandationRepository = $this->createMock(RecommandationRepository::class);

        $recommandation1 = $this->createMock(Recommandation::class);
        $recommandation1->method('getId')->willReturn(1);
        $recommandation1->codeReco = 'OQU8A';
        $recommandation1->dateReco = new \DateTimeImmutable();
        $recommandation1->espece = 'chien';
        $recommandation1->petname = 'kiki';
        $recommandation1->race = 'Border collier';
        $recommandation1->idGender = 1;
        $recommandation1->activite = 'Calme';
        $recommandation1->dateBirth = '25-10-2020';
        $recommandation1->stadePhysio = 'adulte';
        $recommandation1->pathologie = '';
        $recommandation1->poidsActuel = '16';
        $recommandation1->poidsIdeal = '15';
        $recommandation1->be = 571;
        $recommandation1->nbrAliment = 1;

        $animal = $this->createMock(Animal::class);
        $animal->method('getId')->willReturn(1);
        $animal->espece = 'chien';
        $animal->nom = 'kiki';
        $animal->idCustomer = 10;
        $animal->idGender = 2;
        $recommandation1->animal = $animal;

        $produit1 = $this->createMock(RecommandationProduct::class);
        $produit1->method('getId')->willReturn(1);
        $produit1->idProduct = 10;
        $produit1->months = null;
        $produit1->quantity = 127;
        $recommandation1->method('getProduits')->willReturn(new ArrayCollection([$produit1]));

        $recommandationRepository
            ->expects($this->once())
            ->method('findBy')
            ->with(['idCustomer' => 1, 'deleted' => false])
            ->willReturn([
                $recommandation1,
            ]);

        self::$kernel->getContainer()->set(RecommandationRepository::class, $recommandationRepository);

        // Remplace le AnimalRepository par un mock
        $animalRepository = $this->createMock(AnimalRepository::class);

        $animal = $this->createMock(Animal::class);
        $animal->method('getId')->willReturn(1);
        $animal->espece = 'chien';
        $animal->nom = 'kiki';

        $animalRepository
            ->expects($this->once())
            ->method('findBy')
            ->with(['idCustomer' => 1, 'deleted' => false])
            ->willReturn([
                $animal,
            ]);

        self::$kernel->getContainer()->set(AnimalRepository::class, $animalRepository);

        // Partial Mock du RecommandationProductPresenter
        $recommandationProductPresenter = $this->getMockBuilder(RecommandationProductPresenter::class)
            ->setConstructorArgs([self::$kernel->getContainer()->get('prestashop.adapter.legacy.context')])
            ->onlyMethods(['getCover', 'getProduct'])
            ->getMock();

        $recommandationProductPresenter
            ->expects($this->once())
            ->method('getCover')
            ->with(10)
            ->willReturn(['id_image' => 5, 'id_product' => 10, 'position' => 1, 'cover' => 1]);

        $product10 = $this->createMock(\Product::class);
        $product10->id = 10;
        $product10->name = 'Produit 10';

        $recommandationProductPresenter
            //->expects($this->once())
            ->method('getProduct')
            ->with(10)
            ->willReturn($product10);

        self::$kernel->getContainer()->set(RecommandationProductPresenter::class, $recommandationProductPresenter);

        $this->controller->initContent();

        \ob_start();
        $this->controller->display();
        $html = \ob_get_clean() ?: '';

        $this->assertStringContainsString('OQU8A', $html);
        $this->assertStringContainsString('Ration : 127g/jour', $html);
    }

    public function testConnectedImportCodePartOne(): void
    {
        \assert($this->context->customer instanceof MockObject);

        $this->context->customer->id = 1;
        $this->context->customer
            ->method('isLogged')
            ->willReturn(true);

        // Remplace le RecommandationRepository par un mock
        $recommandationRepository = $this->createMock(RecommandationRepository::class);

        $recommandationRepository
            ->expects($this->once())
            ->method('findBy')
            ->with(['idCustomer' => 1, 'deleted' => false])
            ->willReturn([]);

        self::$kernel->getContainer()->set(RecommandationRepository::class, $recommandationRepository);

        // Remplace le AnimalRepository par un mock
        $animalRepository = $this->createMock(AnimalRepository::class);

        $animal = $this->createMock(Animal::class);
        $animal->method('getId')->willReturn(1);
        $animal->espece = 'chien';
        $animal->nom = 'kiki';

        $animalRepository
            ->expects($this->once())
            ->method('findBy')
            ->with(['idCustomer' => 1, 'deleted' => false])
            ->willReturn([
                $animal,
            ]);

        self::$kernel->getContainer()->set(AnimalRepository::class, $animalRepository);

        $recommandationDto = new CrokitRecommandation();
        $recommandationDto->codeReco = '123456';
        $recommandationDto->esp = 'chien';
        $recommandationDto->petname = 'Medor';
        $recommandationDto->sex = 'Mâle';
        $recommandationDto->dob = '12 mai';
        $recommandationDto->date = new \DateTimeImmutable();
        $recommandationDto->race = 'Terre-Neuve';
        $recommandationDto->productIDmvs1 = 10638;
        $recommandationDto->qteAliment1 = 145;
        $recommandationDto->nbrAliment = 1;
        $recommandationDto->code = 'AAA111';

        $crokitApi = $this->createMock(CrokitApiClient::class);
        $crokitApi
            ->expects($this->once())
            ->method('getRecommandation')
            ->with('OQU8A')
            ->willReturn($recommandationDto);

        // Remplace l'API Crokit par un mock
        self::$kernel->getContainer()->set(CrokitApiClient::class, $crokitApi);

        $recommandationProductPresenter = $this->getMockBuilder(RecommandationProductPresenter::class)
            ->setConstructorArgs([self::$kernel->getContainer()->get('prestashop.adapter.legacy.context')])
            ->onlyMethods(['presentDto'])
            ->getMock();

        $recommandationProductPresenter
            ->expects($this->once())
            ->method('presentDto')
            ->willReturn([
                'id' => 0,
                'months' => null,
                'quantity' => 127,
                'product' => $this->createMock(\Product::class),
                'image_id' => 10,
            ]);

        self::$kernel->getContainer()->set(RecommandationProductPresenter::class, $recommandationProductPresenter);

        $_GET['code'] = 'OQU8A';
        $this->controller->initContent();

        \ob_start();
        $this->controller->display();
        $html = \ob_get_clean() ?: '';

        $this->assertStringContainsString('value="OQU8A"', $html);
        $this->assertStringContainsString('<option value="1">kiki (chien)</option>', $html);
        $this->assertStringContainsString('<option value="auto">Nouvel animal</option>', $html);
        $this->assertStringContainsString('Aucune recommandation enregistrée', $html);
    }

    public function testConnectedImportCodeAutoAnimal(): void
    {
        \assert($this->context->customer instanceof MockObject);

        $this->context->customer->id = 1;
        $this->context->customer
            ->method('isLogged')
            ->willReturn(true);

        // Remplace le RecommandationRepository par un mock
        $recommandationRepository = $this->createMock(RecommandationRepository::class);

        $recommandationRepository
            ->expects($this->once())
            ->method('findBy')
            ->with(['idCustomer' => 1, 'deleted' => false])
            ->willReturn([]);

        self::$kernel->getContainer()->set(RecommandationRepository::class, $recommandationRepository);

        // Remplace le AnimalRepository par un mock
        $animalRepository = $this->createMock(AnimalRepository::class);

        $animal = $this->createMock(Animal::class);
        $animal->method('getId')->willReturn(1);
        $animal->espece = 'chien';
        $animal->nom = 'kiki';

        $animalRepository
            ->expects($this->once())
            ->method('findBy')
            ->with(['idCustomer' => 1, 'deleted' => false])
            ->willReturn([
                $animal,
            ]);

        self::$kernel->getContainer()->set(AnimalRepository::class, $animalRepository);

        $recommandationDto = new CrokitRecommandation();
        $recommandationDto->codeReco = 'OQU8A';
        $recommandationDto->code = 'AAA111';
        $recommandationDto->esp = 'chien';
        $recommandationDto->petname = 'kiki';
        $recommandationDto->date = new \DateTimeImmutable();
        $recommandationDto->race = 'Border collier';
        $recommandationDto->sex = 'mâle castré';
        $recommandationDto->act = 'Calme';
        $recommandationDto->dob = '25-10-2020';
        $recommandationDto->stadePhysio = 'adulte';
        $recommandationDto->patho = '';
        $recommandationDto->actualw = 16;
        $recommandationDto->idealw = 15;
        $recommandationDto->be = 571;
        $recommandationDto->nbrAliment = 1;
        $recommandationDto->productIDmvs1 = 10638;
        $recommandationDto->productIDmvs2 = null;
        $recommandationDto->productIDmvs3 = null;
        $recommandationDto->qteAliment1 = 145;
        $recommandationDto->qteAliment2 = null;
        $recommandationDto->qteAliment3 = null;
        $recommandationDto->transition12 = null;
        $recommandationDto->transition23 = null;

        $crokitApi = $this->createMock(CrokitApiClient::class);
        $crokitApi
            ->expects($this->once())
            ->method('getRecommandation')
            ->with('OQU8A')
            ->willReturn($recommandationDto);

        // Remplace l'API Crokit par un mock
        self::$kernel->getContainer()->set(CrokitApiClient::class, $crokitApi);

        // Remplace l'entity manager
        $entityManager = $this->createMock(EntityManagerInterface::class);
        $entityManager
            ->expects($this->once())
            ->method('persist')
            ->with($this->isInstanceOf(Recommandation::class));
        self::$kernel->getContainer()->set('doctrine.orm.default_entity_manager', $entityManager);

        $this->recommandationCartRuleRepository
            ->expects($this->once())
            ->method('createCartRule');

        // Partial Mock du RecommandationProductPresenter
        $recommandationProductPresenter = $this->getMockBuilder(RecommandationProductPresenter::class)
            ->setConstructorArgs([self::$kernel->getContainer()->get('prestashop.adapter.legacy.context')])
            ->onlyMethods(['getCover', 'getProduct'])
            ->getMock();

        $recommandationProductPresenter
            ->expects($this->once())
            ->method('getCover')
            ->willReturn(['id_image' => 5, 'id_product' => 10, 'position' => 1, 'cover' => 1]);

        $product10 = $this->createMock(\Product::class);
        $product10->id = 10;
        $product10->name = 'Produit 10';

        $recommandationProductPresenter
            ->expects($this->once())
            ->method('getProduct')
            ->willReturn($product10);

        self::$kernel->getContainer()->set(RecommandationProductPresenter::class, $recommandationProductPresenter);

        $_GET['code'] = 'OQU8A';
        $_GET['id_animal'] = 'auto';
        $this->controller->initContent();

        \ob_start();
        $this->controller->display();
        $html = \ob_get_clean() ?: '';

        $this->assertStringContainsString('OQU8A', $html);
        $this->assertStringContainsString('Ration : 145g/jour', $html);
    }
}
