<?php

declare(strict_types=1);

/*
 * Chill is a software for social workers
 *
 * For the full copyright and license information, please view
 * the LICENSE file that was distributed with this source code.
 */

namespace Chill\PersonBundle\Tests\Service\AccompanyingPeriodWork;

use Chill\MainBundle\Entity\User;
use Chill\PersonBundle\Entity\AccompanyingPeriod\AccompanyingPeriodWork;
use Chill\PersonBundle\Entity\AccompanyingPeriod\AccompanyingPeriodWorkEvaluation;
use Chill\PersonBundle\Service\AccompanyingPeriodWork\AccompanyingPeriodWorkMergeService;
use Chill\ThirdPartyBundle\Entity\ThirdParty;
use Doctrine\DBAL\Connection;
use Doctrine\ORM\EntityManagerInterface;
use Prophecy\Argument;
use Prophecy\PhpUnit\ProphecyTrait;
use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase;

/**
 * @internal
 *
 * @coversNothing
 */
class AccompanyingPeriodWorkMergeServiceTest extends KernelTestCase
{
    use ProphecyTrait;

    private function buildMergeService(AccompanyingPeriodWork $toRemove): AccompanyingPeriodWorkMergeService
    {
        $entityManager = $this->prophesize(EntityManagerInterface::class);
        $entityManager->wrapInTransaction(Argument::type('callable'))->will(function ($args) use ($entityManager) {
            call_user_func_array($args[0], [$entityManager->reveal()]);
        })->shouldBeCalled();
        $entityManager->remove($toRemove)->shouldBeCalled();

        $connection = $this->prophesize(Connection::class);
        $connection->executeQuery(Argument::type('string'), Argument::type('array'))->shouldBeCalled();

        $entityManager->getConnection()->willReturn($connection->reveal());

        return new AccompanyingPeriodWorkMergeService($entityManager->reveal());
    }

    /**
     * @dataProvider provideStartDateMoveData
     */
    public function testStartDateMove(AccompanyingPeriodWork $toKeep, AccompanyingPeriodWork $toDelete, ?\DateTime $expected): void
    {
        $service = $this->buildMergeService($toDelete);
        $return = $service->merge($toKeep, $toDelete);

        self::assertEquals($expected, $return->getStartDate());
    }

    public static function provideStartDateMoveData(): array
    {
        return [
            'Earliest date kept when toKeep is earlier' => [
                (new AccompanyingPeriodWork())->setStartDate(new \DateTime('2023-01-01')),
                (new AccompanyingPeriodWork())->setStartDate(new \DateTime('2023-06-01')),
                new \DateTime('2023-01-01'),
            ],
            'Earliest date kept when toDelete is earlier' => [
                (new AccompanyingPeriodWork())->setStartDate(new \DateTime('2023-06-01')),
                (new AccompanyingPeriodWork())->setStartDate(new \DateTime('2023-01-01')),
                new \DateTime('2023-01-01'),
            ],
            'Same start dates remain unchanged' => [
                (new AccompanyingPeriodWork())->setStartDate(new \DateTime('2023-01-01')),
                (new AccompanyingPeriodWork())->setStartDate(new \DateTime('2023-01-01')),
                new \DateTime('2023-01-01'),
            ],
        ];
    }

    /**
     * @dataProvider provideEndDateMoveData
     */
    public function testEndDateMove(AccompanyingPeriodWork $toKeep, AccompanyingPeriodWork $toDelete, ?\DateTimeImmutable $expected): void
    {
        $service = $this->buildMergeService($toDelete);
        $return = $service->merge($toKeep, $toDelete);

        self::assertEquals($expected, $return->getEndDate());
    }

    public static function provideEndDateMoveData(): array
    {
        return [
            'Oldest date kept when toKeep is older' => [
                (new AccompanyingPeriodWork())->setEndDate(new \DateTimeImmutable('2022-01-01'))->setStartDate(new \DateTime('2021-01-01')),
                (new AccompanyingPeriodWork())->setEndDate(new \DateTimeImmutable('2023-06-01'))->setStartDate(new \DateTime('2021-01-01')),
                new \DateTimeImmutable('2023-06-01'),
            ],
            'Oldest date kept when toDelete is older' => [
                (new AccompanyingPeriodWork())->setEndDate(new \DateTimeImmutable('2023-06-01'))->setStartDate(new \DateTime('2021-01-01')),
                (new AccompanyingPeriodWork())->setEndDate(new \DateTimeImmutable('2022-01-01'))->setStartDate(new \DateTime('2021-01-01')),
                new \DateTimeImmutable('2023-06-01'),
            ],
            'Same end dates remain unchanged' => [
                (new AccompanyingPeriodWork())->setEndDate(new \DateTimeImmutable('2023-01-01'))->setStartDate(new \DateTime('2021-01-01')),
                (new AccompanyingPeriodWork())->setEndDate(new \DateTimeImmutable('2023-01-01'))->setStartDate(new \DateTime('2021-01-01')),
                new \DateTimeImmutable('2023-01-01'),
            ],
            'End date is null if toKeep is null' => [
                (new AccompanyingPeriodWork())->setEndDate(null)->setStartDate(new \DateTime('2021-01-01')),
                (new AccompanyingPeriodWork())->setEndDate(new \DateTimeImmutable('2023-01-01'))->setStartDate(new \DateTime('2021-01-01')),
                null,
            ],
            'End date is null if toDelete is null' => [
                (new AccompanyingPeriodWork())->setEndDate(new \DateTimeImmutable('2023-01-01'))->setStartDate(new \DateTime('2021-01-01')),
                (new AccompanyingPeriodWork())->setEndDate(null)->setStartDate(new \DateTime('2021-01-01')),
                null,
            ],
            'End date is null if both are null' => [
                (new AccompanyingPeriodWork())->setEndDate(null)->setStartDate(new \DateTime('2021-01-01')),
                (new AccompanyingPeriodWork())->setEndDate(null)->setStartDate(new \DateTime('2021-01-01')),
                null,
            ],
        ];
    }

    /**
     * @dataProvider provideMoveHandlingThirdPartyData
     */
    public function testMoveHandlingThirdParty(AccompanyingPeriodWork $toKeep, AccompanyingPeriodWork $toDelete, ?ThirdParty $expected): void
    {
        $service = $this->buildMergeService($toDelete);
        $return = $service->merge($toKeep, $toDelete);

        self::assertSame($expected, $return->getHandlingThierParty());
    }

    public static function provideMoveHandlingThirdPartyData(): iterable
    {
        yield 'Third party not change when existing in kept' => [
            (new AccompanyingPeriodWork())->setStartDate(new \DateTimeImmutable('2022-01-01'))->setHandlingThierParty($tpA = new ThirdParty()),
            (new AccompanyingPeriodWork())->setStartDate(new \DateTimeImmutable('2022-01-01'))->setHandlingThierParty(new ThirdParty()),
            $tpA,
        ];

        yield 'Third party will change when not existing in kept' => [
            (new AccompanyingPeriodWork())->setStartDate(new \DateTimeImmutable('2022-01-01')),
            (new AccompanyingPeriodWork())->setStartDate(new \DateTimeImmutable('2022-01-01'))->setHandlingThierParty($tpB = new ThirdParty()),
            $tpB,
        ];

        yield 'Third party do not change when not existing in removed' => [
            (new AccompanyingPeriodWork())->setStartDate(new \DateTimeImmutable('2022-01-01'))->setHandlingThierParty($tpC = new ThirdParty()),
            (new AccompanyingPeriodWork())->setStartDate(new \DateTimeImmutable('2022-01-01')),
            $tpC,
        ];
    }

    public function testMergeAccompanyingPeriodWorks(): void
    {
        $em = self::getContainer()->get(EntityManagerInterface::class);

        $userA = new User();
        $userA->setUsername('someUser');
        $userA->setEmail('someUser@example.com');
        $em->persist($userA);

        $toKeep = new AccompanyingPeriodWork();
        $toKeep->setStartDate(new \DateTime('2022-01-02'));
        $toKeep->setNote('Keep note');
        $toKeep->setCreatedBy($userA);
        $toKeep->setUpdatedBy($userA);
        $toKeep->addReferrer($userA);
        $em->persist($toKeep);

        $userB = new User();
        $userB->setUsername('anotherUser');
        $userB->setEmail('anotherUser@example.com');
        $em->persist($userB);

        $toDelete = new AccompanyingPeriodWork();
        $toDelete->setStartDate(new \DateTime('2022-01-01'));
        $toDelete->setNote('Delete note');
        $toDelete->setCreatedBy($userB);
        $toDelete->setUpdatedBy($userB);
        $toDelete->addReferrer($userB);
        $em->persist($toDelete);

        $evaluation = new AccompanyingPeriodWorkEvaluation();
        $evaluation->setAccompanyingPeriodWork($toDelete);
        $em->persist($evaluation);

        $em->flush();

        $service = new AccompanyingPeriodWorkMergeService($em);
        $merged = $service->merge($toKeep, $toDelete);

        $em->refresh($merged);

        // Assertions

        $this->assertEquals(new \DateTime('2022-01-01'), $merged->getStartDate());

        $this->assertStringContainsString('Keep note', $merged->getNote());
        $this->assertStringContainsString('Delete note', $merged->getNote());

        $em->refresh($evaluation);
        $this->assertEquals($toKeep->getId(), $evaluation->getAccompanyingPeriodWork()->getId());

        $em->remove($evaluation);
        $em->remove($toKeep);
        $em->remove($toDelete);
        $em->remove($userA);
        $em->remove($userB);
        $em->flush();
    }
}
