<?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 AccompanyingPeriod\Events;

use Chill\MainBundle\Entity\Address;
use Chill\MainBundle\Entity\Notification;
use Chill\MainBundle\Entity\User;
use Chill\MainBundle\Notification\NotificationPersister;
use Chill\MainBundle\Notification\NotificationPersisterInterface;
use Chill\PersonBundle\AccompanyingPeriod\Events\PersonAddressMoveEventSubscriber;
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\Event\Person\PersonAddressMoveEvent;
use Prophecy\Argument;
use Prophecy\PhpUnit\ProphecyTrait;
use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase;
use Symfony\Component\Security\Core\Security;
use Symfony\Contracts\Translation\TranslatorInterface;

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

    public function testEventChangeHouseholdNoNotificationForPeriodWithoutUser()
    {
        $person = new Person();
        $period = new AccompanyingPeriod();
        $period
            ->setStep(AccompanyingPeriod::STEP_CONFIRMED)
            ->setPersonLocation($person)
            ->addPerson($person);
        $this->forceIdToPeriod($period);

        $previousHousehold = (new Household())->addAddress(
            (new Address())->setValidFrom(new \DateTime('1 year ago'))
        );
        $previousMembership = new HouseholdMember();
        $previousMembership
            ->setPerson($person)
            ->setHousehold($previousHousehold)
            ->setStartDate(new \DateTimeImmutable('1 year ago'))
            ->setEndDate(new \DateTimeImmutable('tomorrow'));

        $nextHousehold = (new Household())->addAddress(
            (new Address())->setValidFrom(new \DateTime('tomorrow'))
        );
        $nextMembership = new HouseholdMember();
        $nextMembership
            ->setPerson($person)
            ->setHousehold($nextHousehold)
            ->setStartDate(new \DateTimeImmutable('tomorrow'));

        $event = new PersonAddressMoveEvent($person);
        $event
            ->setPreviousMembership($previousMembership)
            ->setNextMembership($nextMembership);

        $notificationPersister = $this->prophesize(NotificationPersisterInterface::class);
        $notificationPersister->persist(Argument::type(Notification::class))->shouldNotBeCalled();
        $eventSubscriber = $this->buildSubscriber(null, $notificationPersister->reveal(), null, null);

        $eventSubscriber->resetPeriodLocation($event);
    }

    public function testEventChangeHouseholdNotification()
    {
        $person = new Person();
        $period = new AccompanyingPeriod();
        $period
            ->setStep(AccompanyingPeriod::STEP_CONFIRMED)
            ->setPersonLocation($person)
            ->addPerson($person)
            ->setUser(new User());
        $this->forceIdToPeriod($period);

        $previousHousehold = (new Household())->addAddress(
            ($previousAddress = new Address())->setValidFrom(new \DateTime('1 year ago'))
        );
        $previousMembership = new HouseholdMember();
        $previousMembership
            ->setPerson($person)
            ->setHousehold($previousHousehold)
            ->setStartDate(new \DateTimeImmutable('1 year ago'))
            ->setEndDate(new \DateTimeImmutable('tomorrow'));

        $nextHousehold = (new Household())->addAddress(
            (new Address())->setValidFrom(new \DateTime('tomorrow'))
        );
        $nextMembership = new HouseholdMember();
        $nextMembership
            ->setPerson($person)
            ->setHousehold($nextHousehold)
            ->setStartDate(new \DateTimeImmutable('tomorrow'));

        $event = new PersonAddressMoveEvent($person);
        $event
            ->setPreviousMembership($previousMembership)
            ->setNextMembership($nextMembership);

        $notificationPersister = $this->prophesize(NotificationPersisterInterface::class);
        $notificationPersister->persist(Argument::type(Notification::class))->shouldBeCalledTimes(1);
        $eventSubscriber = $this->buildSubscriber(null, $notificationPersister->reveal(), null, null);

        $eventSubscriber->resetPeriodLocation($event);

        $this->assertNotNull($period->getAddressLocation());
        $this->assertNull($period->getPersonLocation());
    }

    public function testEventChangeHouseholdNotificationForPeriodChangeLocationOfPersonAnteriorToCurrentLocationHistory()
    {
        $person = new Person();
        $period = new AccompanyingPeriod();
        $period
            ->setStep(AccompanyingPeriod::STEP_CONFIRMED)
            ->setPersonLocation($person)
            ->setUser(new User())
            ->addPerson($person);
        $this->forceIdToPeriod($period);

        $previousHousehold = (new Household())->addAddress(
            ($previousAddress = new Address())->setValidFrom(new \DateTime('1 year ago'))
        );
        $previousMembership = new HouseholdMember();
        $previousMembership
            ->setPerson($person)
            ->setHousehold($previousHousehold)
            ->setStartDate(new \DateTimeImmutable('1 year ago'))
            ->setEndDate(new \DateTimeImmutable('tomorrow'));

        $nextHousehold = (new Household())->addAddress(
            (new Address())->setValidFrom(new \DateTime('1 month ago'))
        );
        $nextMembership = new HouseholdMember();
        $nextMembership
            ->setPerson($person)
            ->setHousehold($nextHousehold)
            ->setStartDate(new \DateTimeImmutable('1 month ago'));

        $event = new PersonAddressMoveEvent($person);
        $event
            ->setPreviousMembership($previousMembership)
            ->setNextMembership($nextMembership);

        $notificationPersister = $this->prophesize(NotificationPersisterInterface::class);
        $notificationPersister->persist(Argument::type(Notification::class))->shouldBeCalled();
        $eventSubscriber = $this->buildSubscriber(null, $notificationPersister->reveal(), null, null);

        $eventSubscriber->resetPeriodLocation($event);

        $this->assertNotNull($period->getAddressLocation());
        $this->assertNull($period->getPersonLocation());
    }

    public function testEventLeaveNotification()
    {
        $person = new Person();
        $period = new AccompanyingPeriod();
        $period
            ->setStep(AccompanyingPeriod::STEP_CONFIRMED)
            ->setPersonLocation($person)
            ->addPerson($person)
            ->setUser(new User());
        $this->forceIdToPeriod($period);

        $previousHousehold = (new Household())->addAddress(
            ($previousAddress = new Address())->setValidFrom(new \DateTime('1 year ago'))
        );
        $previousMembership = new HouseholdMember();
        $previousMembership
            ->setPerson($person)
            ->setHousehold($previousHousehold)
            ->setStartDate(new \DateTimeImmutable('1 year ago'))
            ->setEndDate(new \DateTimeImmutable('tomorrow'));

        $event = new PersonAddressMoveEvent($person);
        $event
            ->setPreviousMembership($previousMembership);

        $notificationPersister = $this->prophesize(NotificationPersisterInterface::class);
        $notificationPersister->persist(Argument::type(Notification::class))->shouldBeCalledTimes(1);
        $eventSubscriber = $this->buildSubscriber(null, $notificationPersister->reveal(), null, null);

        $eventSubscriber->resetPeriodLocation($event);

        $this->assertNotNull($period->getAddressLocation());
        $this->assertNull($period->getPersonLocation());
    }

    public function testEventPersonChangeAddressInThePast()
    {
        $person = new Person();
        $period = new AccompanyingPeriod();
        $period
            ->setStep(AccompanyingPeriod::STEP_CONFIRMED)
            ->setPersonLocation($person)
            ->addPerson($person)
            ->setUser(new User());
        $this->forceIdToPeriod($period);

        $membership = new HouseholdMember();
        $membership
            ->setPerson($person)
            ->setHousehold($household = new Household())
            ->setStartDate(new \DateTimeImmutable('1 year ago'));

        $previousAddress = new Address();
        $previousAddress->setValidFrom(new \DateTime('6 months ago'));
        $household->addAddress($previousAddress);

        $newAddress = new Address();
        $newAddress->setValidFrom(new \DateTime('tomorrow'));
        $household->addAddress($newAddress);

        $event = new PersonAddressMoveEvent($person);
        $event
            ->setPreviousAddress($household->getPreviousAddressOf($newAddress))
            ->setNextAddress($newAddress);

        $notificationPersister = $this->prophesize(NotificationPersisterInterface::class);
        $notificationPersister->persist(Argument::type(Notification::class))->shouldBeCalledTimes(1);
        $eventSubscriber = $this->buildSubscriber(null, $notificationPersister->reveal(), null, null);

        $eventSubscriber->resetPeriodLocation($event);

        $this->assertNotNull($period->getAddressLocation());
        $this->assertNull($period->getPersonLocation());
    }

    private function buildSubscriber(
        ?\Twig\Environment $engine = null,
        ?NotificationPersisterInterface $notificationPersister = null,
        ?Security $security = null,
        ?TranslatorInterface $translator = null,
    ): PersonAddressMoveEventSubscriber {
        if (null === $translator) {
            $double = $this->prophesize(TranslatorInterface::class);
            $translator = $double->reveal();
        }

        if (null === $security) {
            $double = $this->prophesize(Security::class);
            $double->getUser()->willReturn(new User());
            $security = $double->reveal();
        }

        if (null === $engine) {
            $double = $this->prophesize(\Twig\Environment::class);
            $double->render(Argument::type('string'), Argument::type('array'))->willReturn('dummy string');
            $engine = $double->reveal();
        }

        if (null === $notificationPersister) {
            $notificationPersister = new NotificationPersister();
        }

        return new PersonAddressMoveEventSubscriber(
            $engine,
            $notificationPersister,
            $security,
            $translator
        );
    }

    private function forceIdToPeriod(AccompanyingPeriod $period): void
    {
        $reflectionClass = new \ReflectionClass($period);
        $property = $reflectionClass->getProperty('id');
        $property->setAccessible(true);
        $property->setValue($period, 0);
    }
}
