<?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\Entity;

use Chill\MainBundle\Entity\Address;
use Chill\MainBundle\Entity\User;
use Chill\PersonBundle\Entity\AccompanyingPeriod;
use Chill\PersonBundle\Entity\AccompanyingPeriod\Comment;
use Chill\PersonBundle\Entity\AccompanyingPeriodParticipation;
use Chill\PersonBundle\Entity\Person;
use Chill\ThirdPartyBundle\Entity\ThirdParty;

/**
 * @internal
 *
 * @coversNothing
 */
final class AccompanyingPeriodTest extends \PHPUnit\Framework\TestCase
{
    public function testChangeStepKeepHistory()
    {
        $period = new AccompanyingPeriod();

        $this->assertCount(0, $period->getStepHistories(), 'at initialization, period should not have any step history');

        $period->setStep(AccompanyingPeriod::STEP_DRAFT);

        $this->assertCount(0, $period->getStepHistories(), 're applying a draft should not create a history');

        $period->setStep(AccompanyingPeriod::STEP_CONFIRMED);

        $this->assertCount(1, $period->getStepHistories());
        $this->assertEquals(AccompanyingPeriod::STEP_CONFIRMED, $period->getStepHistories()->first()->getStep());

        $period->setOpeningDate($aMonthAgo = new \DateTime('1 month ago'));

        $this->assertCount(1, $period->getStepHistories());
        $this->assertEquals($aMonthAgo, $period->getStepHistories()->first()->getStartDate(), 'when changing the opening date, the start date of the first history should change');

        $period->setOpeningDate($tenDaysAgo = new \DateTime('10 days ago'));

        $this->assertCount(1, $period->getStepHistories());
        $this->assertEquals($tenDaysAgo, $period->getStepHistories()->first()->getStartDate(), 'when changing the opening date, the start date of the first history should change');

        $period->setStep(AccompanyingPeriod::STEP_CLOSED);
        $this->assertCount(2, $period->getStepHistories());

        $period->setOpeningDate($tomorrow = new \DateTime('tomorrow'));
        $this->assertEquals($tenDaysAgo, $period->getStepHistories()->first()->getStartDate(), 'when changing the opening date to a later one and no history after, start date should change');
    }

    public function testClosingEqualOpening()
    {
        $datetime = new \DateTime('now');

        $period = new AccompanyingPeriod($datetime);
        $period->setClosingDate($datetime);

        $this->assertTrue($period->isClosingAfterOpening());
    }

    public function testClosingIsAfterOpeningConsistency()
    {
        $datetime1 = new \DateTime('now');
        $datetime2 = new \DateTime('tomorrow');

        $period = new AccompanyingPeriod($datetime1);
        $period->setClosingDate($datetime2);

        $r = $period->isClosingAfterOpening();

        $this->assertTrue($r);
    }

    public function testClosingIsBeforeOpeningConsistency()
    {
        $datetime1 = new \DateTime('tomorrow');
        $datetime2 = new \DateTime('now');

        $period = new AccompanyingPeriod($datetime1);
        $period->setClosingDate($datetime2);

        $this->assertFalse($period->isClosingAfterOpening());
    }

    public function testHasChangedUser()
    {
        $period = new AccompanyingPeriod();

        $this->assertFalse($period->isChangedUser());
        $this->assertFalse($period->hasPreviousUser());

        $period->setUser($user1 = new User());

        $this->assertTrue($period->isChangedUser());
        $this->assertFalse($period->hasPreviousUser());

        $period->resetPreviousUser();
        $this->assertFalse($period->isChangedUser());
        $this->assertFalse($period->hasPreviousUser());

        $period->setUser($user2 = new User());

        $this->assertTrue($period->isChangedUser());
        $this->assertTrue($period->hasPreviousUser());
        $this->assertSame($user1, $period->getPreviousUser());
    }

    public function testHistoryLocation()
    {
        $period = new AccompanyingPeriod();
        $person = new Person();
        $address = new Address();

        $period->setPersonLocation($person);

        $this->assertCount(0, $period->getLocationHistories());

        $period->setAddressLocation($address);
        $period->setPersonLocation(null);

        $this->assertCount(0, $period->getLocationHistories());

        $period->setStep(AccompanyingPeriod::STEP_CONFIRMED);

        $this->assertCount(1, $period->getLocationHistories());

        $this->assertSame($address, $period->getLocationHistories()->first()->getAddressLocation());
        $this->assertNull($period->getLocationHistories()->first()->getPersonLocation());

        $period->setPersonLocation($person);
        $period->setAddressLocation(null);

        $this->assertCount(2, $period->getLocationHistories());
        $this->assertSame($person, $period->getLocationHistories()->last()->getPersonLocation());
        $this->assertNull($period->getLocationHistories()->last()->getAddressLocation());

        $period->setAddressLocation($address);
        $period->setPersonLocation(null);

        $this->assertCount(3, $period->getLocationHistories());

        $locations = $period->getLocationHistories()->toArray();

        usort($locations, static fn (AccompanyingPeriod\AccompanyingPeriodLocationHistory $a, AccompanyingPeriod\AccompanyingPeriodLocationHistory $b) => $a->getStartDate() <=> $b->getStartDate());

        $iterator = new \ArrayIterator($locations);
        $iterator->rewind();

        do {
            $current = $iterator->current();

            $iterator->next();

            if ($iterator->valid()) {
                $next = $iterator->current();
                $this->assertNotNull($current->getEndDate());
                $this->assertEquals($current->getEndDate(), $next->getStartDate());
            } else {
                $this->assertNull($current->getEndDate());
            }
        } while ($iterator->valid());
    }

    public function testHistoryLocationNotHavingBothAtStart()
    {
        $period = new AccompanyingPeriod();
        $person = new Person();
        $address = new Address();

        $period->setAddressLocation($address);
        $period->setPersonLocation($person);

        $period->setStep(AccompanyingPeriod::STEP_CONFIRMED);

        $this->assertCount(1, $period->getLocationHistories());

        self::assertNull($period->getAddressLocation());
        self::assertNull($period->getLocationHistories()->first()->getAddressLocation());
        self::assertSame($person, $period->getLocationHistories()->first()->getPersonLocation());
        self::assertSame($person, $period->getPersonLocation());
        self::assertEquals('person', $period->getLocationStatus());
    }

    public function testIsClosed()
    {
        $period = new AccompanyingPeriod(new \DateTime());
        $period->setClosingDate(new \DateTime('tomorrow'));

        $this->assertFalse($period->isOpen());
    }

    public function testIsOpen()
    {
        $period = new AccompanyingPeriod(new \DateTime());

        $this->assertTrue($period->isOpen());
    }

    public function testPersonPeriod()
    {
        $person = new Person();
        $person2 = new Person();
        $person3 = new Person();
        $period = new AccompanyingPeriod(new \DateTime());

        $participation0 = $period->createParticipationFor($person);
        $period->createParticipationFor($person2);
        $period->createParticipationFor($person3);

        $this->assertNotNull($participation0);
        $this->assertEquals(3, $period->getParticipations()->count());
        $this->assertTrue($period->containsPerson($person));
        $this->assertFalse($period->containsPerson(new Person()));

        $participation = $period->getOpenParticipationContainsPerson($person);
        $participations = $period->getParticipationsContainsPerson($person);
        $this->assertNotNull($participation);
        $this->assertSame($person, $participation->getPerson());
        $this->assertSame($participation, $participation0);
        $this->assertEquals(1, $participations->count());

        $participationL = $period->closeParticipationFor($person);
        $this->assertNotNull($participationL);

        if ($participationL instanceof AccompanyingPeriodParticipation) {
            $this->assertSame($participationL, $participation);
            $this->assertTrue($participationL->getEndDate() instanceof \DateTimeInterface);
        }

        $participation = $period->getOpenParticipationContainsPerson($person);
        $this->assertNull($participation);

        $person4 = new Person();
        $participations4 = $period->getParticipationsContainsPerson($person4);
        $this->assertEquals(0, $participations4->count());
        $participation4 = $period->getOpenParticipationContainsPerson($person4);
        $this->assertNull($participation4);

        $period->addPerson($person4);
        $this->assertInstanceOf(AccompanyingPeriodParticipation::class, $period->getOpenParticipationContainsPerson($person4));
        $this->assertEquals(1, $period->getParticipationsContainsPerson($person4)->count());
        $period->removePerson($person4);
        $this->assertNull($period->getOpenParticipationContainsPerson($person4));
        $this->assertEquals(1, $period->getParticipationsContainsPerson($person4)->count());
    }

    public function testPinnedComment()
    {
        $period = new AccompanyingPeriod(new \DateTime());
        $comment = new Comment();
        $replacingComment = new Comment();

        $period->setPinnedComment(null);
        $this->assertNull($period->getPinnedComment());

        $period->setPinnedComment($comment);

        $this->assertSame($period->getPinnedComment(), $comment);
        $this->assertNull($comment->getAccompanyingPeriod());
        $this->assertEquals(0, \count($period->getComments()));

        $period->setPinnedComment($replacingComment);

        $this->assertSame($period->getPinnedComment(), $replacingComment);
        $this->assertNull($replacingComment->getAccompanyingPeriod());
        $this->assertSame($period, $comment->getAccompanyingPeriod());
        $this->assertEquals(1, \count($period->getComments()));
        $this->assertContains($comment, $period->getComments());

        $period->setPinnedComment(null);

        $this->assertNull($period->getPinnedComment());
        $this->assertSame($period, $comment->getAccompanyingPeriod());
        $this->assertSame($period, $replacingComment->getAccompanyingPeriod());
        $this->assertEquals(2, \count($period->getComments()));
        $this->assertContains($comment, $period->getComments());
        $this->assertContains($replacingComment, $period->getComments());
    }

    public function testRequestor()
    {
        $period = new AccompanyingPeriod(new \DateTime());
        $person = new Person();
        $thirdParty = new ThirdParty();

        $this->assertNull($period->getRequestorThirdParty());
        $this->assertNull($period->getRequestorPerson());
        $this->assertNull($period->getRequestor());

        $period->setRequestor($person);
        $this->assertNull($period->getRequestorThirdParty());
        $this->assertSame($person, $period->getRequestorPerson());
        $this->assertSame($person, $period->getRequestor());

        $period->setRequestor($thirdParty);
        $this->assertNull($period->getRequestorPerson());
        $this->assertSame($thirdParty, $period->getRequestorThirdParty());
        $this->assertSame($thirdParty, $period->getRequestor());

        $period->setRequestor(null);
        $this->assertNull($period->getRequestorThirdParty());
        $this->assertNull($period->getRequestorPerson());
        $this->assertNull($period->getRequestor());
    }

    public function testSetStep(): void
    {
        $period = new AccompanyingPeriod();

        $period->setStep(AccompanyingPeriod::STEP_CONFIRMED);

        self::assertEquals(AccompanyingPeriod::STEP_CONFIRMED, $period->getStep());
        self::assertCount(1, $period->getStepHistories());

        $period->setStep(AccompanyingPeriod::STEP_CONFIRMED_INACTIVE_SHORT);

        self::assertEquals(AccompanyingPeriod::STEP_CONFIRMED_INACTIVE_SHORT, $period->getStep());
        self::assertCount(2, $period->getStepHistories());

        $periodInactiveSteps = $period->getStepHistories()->filter(fn (AccompanyingPeriod\AccompanyingPeriodStepHistory $h) => AccompanyingPeriod::STEP_CONFIRMED_INACTIVE_SHORT === $h->getStep());
        self::assertCount(1, $periodInactiveSteps);

        $period->setStep(AccompanyingPeriod::STEP_CLOSED, ['closing_motive' => $closingMotive = new AccompanyingPeriod\ClosingMotive()]);

        self::assertEquals(AccompanyingPeriod::STEP_CLOSED, $period->getStep());
        self::assertCount(3, $period->getStepHistories());

        $periodClosedSteps = $period->getStepHistories()->filter(fn (AccompanyingPeriod\AccompanyingPeriodStepHistory $h) => AccompanyingPeriod::STEP_CLOSED === $h->getStep());
        self::assertCount(1, $periodClosedSteps);

        $periodClosedStep = $periodClosedSteps->first();

        self::assertSame($closingMotive, $periodClosedStep->getClosingMotive());
    }
}
