<?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 Action\Remove;

use Chill\ActivityBundle\Entity\Activity;
use Chill\MainBundle\Entity\Center;
use Chill\MainBundle\Entity\User;
use Chill\MainBundle\Repository\CenterRepositoryInterface;
use Chill\PersonBundle\Actions\Remove\PersonMove;
use Chill\PersonBundle\Actions\Remove\PersonMoveManager;
use Chill\PersonBundle\Entity\AccompanyingPeriod;
use Chill\PersonBundle\Entity\Household\Household;
use Chill\PersonBundle\Entity\Household\HouseholdMember;
use Chill\PersonBundle\Entity\Person;
use Chill\PersonBundle\Entity\Relationships\Relation;
use Chill\PersonBundle\Entity\Relationships\Relationship;
use Chill\PersonBundle\Repository\Person\PersonCenterHistoryInterface;
use Doctrine\DBAL\Connection;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;

/**
 * @internal
 *
 * @coversNothing
 */
class PersonMoveTest extends KernelTestCase
{
    private EntityManagerInterface $em;

    private PersonMoveManager $personMoveManager;

    private EventDispatcherInterface $eventDispatcher;

    private CenterRepositoryInterface $centerRepository;

    private PersonCenterHistoryInterface $personCenterHistory;

    /**
     * @var list<array{0: class-string, 1: int}>
     */
    private static array $entitiesToDelete = [];

    public function setUp(): void
    {
        self::bootKernel();
        $this->em = self::getContainer()->get(EntityManagerInterface::class);
        $this->personMoveManager = self::getContainer()->get(PersonMoveManager::class);
        $this->eventDispatcher = self::getContainer()->get(EventDispatcherInterface::class);
        $this->centerRepository = self::getContainer()->get(CenterRepositoryInterface::class);
        $this->personCenterHistory = self::getContainer()->get(PersonCenterHistoryInterface::class);
    }

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

        foreach (self::$entitiesToDelete as [$class, $id]) {
            $entity = $em->find($class, $id);

            if (null !== $entity) {
                $em->remove($entity);
            }
        }

        $em->flush();
    }

    /**
     * @dataProvider dataProviderMovePerson
     */
    public function testMovePersonSimple(Person $personA, Person $personB, string $message): void
    {
        $move = new PersonMove($this->em, $this->personMoveManager, $this->eventDispatcher);
        $sqls = $move->getSQL($personA, $personB);
        $this->em->getConnection()->transactional(function (Connection $conn) use ($sqls) {
            foreach ($sqls as $sql) {
                $conn->executeStatement($sql);
            }
        });

        $personsByIdOfA = $this->em->createQuery('SELECT p FROM '.Person::class.' p WHERE p.id = :id')
            ->setParameter('id', $personA->getId())
            ->getResult();
        $personB = $this->em->find(Person::class, $personB->getId());

        self::assertCount(0, $personsByIdOfA);
        self::assertNotNull($personB?->getId(), $message);
    }

    public static function dataProviderMovePerson(): iterable
    {
        self::bootKernel();
        $em = self::getContainer()->get(EntityManagerInterface::class);

        $personA = new Person();
        $personB = new Person();

        $em->persist($personA);
        $em->persist($personB);

        self::$entitiesToDelete[] = [Person::class, $personA];
        self::$entitiesToDelete[] = [Person::class, $personB];

        yield [$personA, $personB, 'move 2 people without any associated data'];

        $personA = new Person();
        $personB = new Person();

        $activity = new Activity();
        $activity->setDate(new \DateTime('today'));
        $activity->addPerson($personA);
        $activity->addPerson($personB);

        $em->persist($personA);
        $em->persist($personB);
        $em->persist($activity);

        self::$entitiesToDelete[] = [Person::class, $personA];
        self::$entitiesToDelete[] = [Person::class, $personB];
        self::$entitiesToDelete[] = [Activity::class, $activity];

        yield [$personA, $personB, 'move 2 people having an activity'];

        $personA = new Person();
        $personB = new Person();
        $household = new Household();
        $household->addMember(
            $memberA = (new HouseholdMember())->setPerson($personA)->setShareHousehold(true)
                ->setStartDate(new \DateTimeImmutable('2023-01-01'))
        );
        $household->addMember(
            $memberB = (new HouseholdMember())->setPerson($personB)->setShareHousehold(true)
                ->setStartDate(new \DateTimeImmutable('2023-01-01'))
        );

        $em->persist($personA);
        $em->persist($personB);
        $em->persist($household);
        $em->persist($memberA);
        $em->persist($memberB);

        self::$entitiesToDelete[] = [HouseholdMember::class, $memberA];
        self::$entitiesToDelete[] = [HouseholdMember::class, $memberB];
        self::$entitiesToDelete[] = [Household::class, $household];
        self::$entitiesToDelete[] = [Person::class, $personA];
        self::$entitiesToDelete[] = [Person::class, $personB];

        yield [$personA, $personB, 'move 2 people having the same household at the same time'];

        $personA = new Person();
        $personB = new Person();
        $parcours = new AccompanyingPeriod();

        $parcours->addPerson($personA);
        $parcours->addPerson($personB);

        $em->persist($personA);
        $em->persist($personB);
        $em->persist($parcours);

        self::$entitiesToDelete[] = [Person::class, $personA];
        self::$entitiesToDelete[] = [Person::class, $personB];
        self::$entitiesToDelete[] = [AccompanyingPeriod::class, $parcours];

        yield [$personA, $personB, 'move 2 people participating to the same parcours'];

        $personA = new Person();
        $personB = new Person();
        $relationship = new Relationship();
        $relation = new Relation();
        $user = (new User())->setUsername(uniqid())->setEmail(uniqid().'@foo.com');

        $relationship->setRelation($relation);
        $relationship->setToPerson($personA);
        $relationship->setFromPerson($personB);
        $relationship->setReverse(false);
        $relationship->setCreatedBy($user);

        $em->persist($personA);
        $em->persist($personB);
        $em->persist($relation);
        $em->persist($user);
        $em->persist($relationship);

        self::$entitiesToDelete[] = [Person::class, $personA];
        self::$entitiesToDelete[] = [Person::class, $personB];
        self::$entitiesToDelete[] = [Relation::class, $relation];
        self::$entitiesToDelete[] = [User::class, $user];
        self::$entitiesToDelete[] = [Relationship::class, $relationship];

        yield [$personA, $personB, 'move 2 people with a relationship'];

        $em->flush();
        $em->clear();
    }

    public function testMovePersonCenterHistory(): void
    {
        $personA = new Person();
        $personB = new Person();
        [$centerA, $centerB] = $this->centerRepository->findAll();

        $this->em->persist($personA);
        $this->em->persist($personB);

        $personCenterHistoryAFirst = (new Person\PersonCenterHistory())->setCenter($centerA)
            ->setStartDate(new \DateTimeImmutable('2023-01-01'))
            ->setEndDate(new \DateTimeImmutable('2023-06-30'));
        $personCenterHistoryASecond = (new Person\PersonCenterHistory())->setCenter($centerB)
            ->setStartDate(new \DateTimeImmutable('2023-06-30'))
            ->setEndDate(new \DateTimeImmutable('2023-09-30'));
        $personCenterHistoryBFirst = (new Person\PersonCenterHistory())->setCenter($centerA)
            ->setStartDate(new \DateTimeImmutable('2023-03-01'))
            ->setEndDate(new \DateTimeImmutable('2023-07-15'));
        $personCenterHistoryBSecond = (new Person\PersonCenterHistory())->setCenter($centerB)
            ->setStartDate(new \DateTimeImmutable('2023-07-15'))
            ->setEndDate(new \DateTimeImmutable('2023-09-30'));

        $this->em->persist($personCenterHistoryAFirst);
        $this->em->persist($personCenterHistoryASecond);
        $this->em->persist($personCenterHistoryBFirst);
        $this->em->persist($personCenterHistoryBSecond);

        $personA->addCenterHistory($personCenterHistoryAFirst);
        $personA->addCenterHistory($personCenterHistoryASecond);
        $personB->addCenterHistory($personCenterHistoryBFirst);
        $personB->addCenterHistory($personCenterHistoryBSecond);

        $this->em->flush();
        $this->em->clear();

        $move = new PersonMove($this->em, $this->personMoveManager, $this->eventDispatcher);
        $sqls = $move->getSQL($personA, $personB);
        $this->em->getConnection()->transactional(function (Connection $conn) use ($sqls) {
            foreach ($sqls as $sql) {
                $conn->executeStatement($sql);
            }
        });

        $personsByIdOfA = $this->em->createQuery('SELECT p FROM '.Person::class.' p WHERE p.id = :id')
            ->setParameter('id', $personA->getId())
            ->getResult();
        /** @var Person $personB */
        $personB = $this->em->find(Person::class, $personB->getId());
        $message = 'Move persons with overlapping center histories';

        self::assertCount(0, $personsByIdOfA);
        self::assertNotNull($personB?->getId(), $message);

        $centersHistories = $this->personCenterHistory->findBy(['person' => $personB]);

        // compute the oldest center history
        $oldestCenterHistory = null;
        foreach ($centersHistories as $centerHistory) {
            $this->em->refresh($centerHistory);
            if (null === $oldestCenterHistory || ($oldestCenterHistory instanceof Person\PersonCenterHistory && $oldestCenterHistory->getStartDate() >= $centerHistory->getStartDate())) {
                $oldestCenterHistory = $centerHistory;
            }
        }

        self::assertCount(2, $centersHistories);
        self::assertEquals('2023-01-01', $oldestCenterHistory?->getStartDate()->format('Y-m-d'));

        self::$entitiesToDelete[] = [Person::class, $personA];
        self::$entitiesToDelete[] = [Person::class, $personB];
        self::$entitiesToDelete[] = [Person\PersonCenterHistory::class, $personCenterHistoryAFirst];
        self::$entitiesToDelete[] = [Person\PersonCenterHistory::class, $personCenterHistoryASecond];
        self::$entitiesToDelete[] = [Person\PersonCenterHistory::class, $personCenterHistoryBFirst];
        self::$entitiesToDelete[] = [Person\PersonCenterHistory::class, $personCenterHistoryBSecond];
    }
}
