<?php

declare(strict_types=1);

namespace Doctrine\Tests\ORM\Functional\Ticket;

use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
use Doctrine\ORM\Mapping\Column;
use Doctrine\ORM\Mapping\Entity;
use Doctrine\ORM\Mapping\GeneratedValue;
use Doctrine\ORM\Mapping\Id;
use Doctrine\ORM\Mapping\JoinColumn;
use Doctrine\ORM\Mapping\ManyToOne;
use Doctrine\ORM\Mapping\OneToMany;
use Doctrine\ORM\PersistentCollection;
use Doctrine\Tests\OrmFunctionalTestCase;
use PHPUnit\Framework\Attributes\Group;

class DDC881Test extends OrmFunctionalTestCase
{
    protected function setUp(): void
    {
        parent::setUp();

        $this->createSchemaForModels(
            DDC881User::class,
            DDC881PhoneNumber::class,
            DDC881PhoneCall::class,
        );
    }

    #[Group('DDC-117')]
    #[Group('DDC-881')]
    public function testIssue(): void
    {
        /* Create two test users: albert and alfons */
        $albert = new DDC881User();
        $albert->setName('albert');
        $this->_em->persist($albert);

        $alfons = new DDC881User();
        $alfons->setName('alfons');
        $this->_em->persist($alfons);

        $this->_em->flush();

        /* Assign two phone numbers to each user */
        $phoneAlbert1 = new DDC881PhoneNumber();
        $phoneAlbert1->setUser($albert);
        $phoneAlbert1->setId(1);
        $phoneAlbert1->setPhoneNumber('albert home: 012345');
        $this->_em->persist($phoneAlbert1);

        $phoneAlbert2 = new DDC881PhoneNumber();
        $phoneAlbert2->setUser($albert);
        $phoneAlbert2->setId(2);
        $phoneAlbert2->setPhoneNumber('albert mobile: 67890');
        $this->_em->persist($phoneAlbert2);

        $phoneAlfons1 = new DDC881PhoneNumber();
        $phoneAlfons1->setId(1);
        $phoneAlfons1->setUser($alfons);
        $phoneAlfons1->setPhoneNumber('alfons home: 012345');
        $this->_em->persist($phoneAlfons1);

        $phoneAlfons2 = new DDC881PhoneNumber();
        $phoneAlfons2->setId(2);
        $phoneAlfons2->setUser($alfons);
        $phoneAlfons2->setPhoneNumber('alfons mobile: 67890');
        $this->_em->persist($phoneAlfons2);

        /* We call alfons and albert once on their mobile numbers */
        $call1 = new DDC881PhoneCall();
        $call1->setPhoneNumber($phoneAlfons2);
        $this->_em->persist($call1);

        $call2 = new DDC881PhoneCall();
        $call2->setPhoneNumber($phoneAlbert2);
        $this->_em->persist($call2);

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

        // fetch-join that foreign-key/primary-key entity association
        $dql   = 'SELECT c, p FROM ' . DDC881PhoneCall::class . ' c JOIN c.phonenumber p';
        $calls = $this->_em->createQuery($dql)->getResult();

        self::assertCount(2, $calls);
        self::assertFalse($this->isUninitializedObject($calls[0]->getPhoneNumber()));
        self::assertFalse($this->isUninitializedObject($calls[1]->getPhoneNumber()));

        $dql     = 'SELECT p, c FROM ' . DDC881PhoneNumber::class . ' p JOIN p.calls c';
        $numbers = $this->_em->createQuery($dql)->getResult();

        self::assertCount(2, $numbers);
        self::assertInstanceOf(PersistentCollection::class, $numbers[0]->getCalls());
        self::assertTrue($numbers[0]->getCalls()->isInitialized());
    }
}

#[Entity]
class DDC881User
{
    #[Id]
    #[Column(type: 'integer')]
    #[GeneratedValue(strategy: 'AUTO')]
    private int $id;

    #[Column(type: 'string', length: 255)]
    private string|null $name = null;

    /** @phpstan-var Collection<int, DDC881PhoneNumber> */
    #[OneToMany(targetEntity: 'DDC881PhoneNumber', mappedBy: 'id')]
    private $phoneNumbers;

    public function getName(): string
    {
        return $this->name;
    }

    public function setName(string $name): void
    {
        $this->name = $name;
    }
}

#[Entity]
class DDC881PhoneNumber
{
    #[Id]
    #[Column(type: 'integer')]
    private int|null $id = null;

    #[Id]
    #[ManyToOne(targetEntity: 'DDC881User', cascade: ['all'])]
    private DDC881User|null $user = null;

    #[Column(type: 'string', length: 255)]
    private string|null $phonenumber = null;

    /** @phpstan-var Collection<int, DDC881PhoneCall> */
    #[OneToMany(targetEntity: 'DDC881PhoneCall', mappedBy: 'phonenumber')]
    private $calls;

    public function __construct()
    {
        $this->calls = new ArrayCollection();
    }

    public function setId(int $id): void
    {
        $this->id = $id;
    }

    public function setUser(DDC881User $user): void
    {
        $this->user = $user;
    }

    public function setPhoneNumber(string $phoneNumber): void
    {
        $this->phonenumber = $phoneNumber;
    }

    /** @phpstan-var Collection<int, DDC881PhoneCall> */
    public function getCalls(): Collection
    {
        return $this->calls;
    }
}

#[Entity]
class DDC881PhoneCall
{
    #[Id]
    #[Column(type: 'integer')]
    #[GeneratedValue(strategy: 'AUTO')]
    private int $id;

    #[JoinColumn(name: 'phonenumber_id', referencedColumnName: 'id')]
    #[JoinColumn(name: 'user_id', referencedColumnName: 'user_id')]
    #[ManyToOne(targetEntity: 'DDC881PhoneNumber', inversedBy: 'calls', cascade: ['all'])]
    private DDC881PhoneNumber|null $phonenumber = null;

    #[Column(type: 'string', nullable: true)]
    private string $callDate;

    public function setPhoneNumber(DDC881PhoneNumber $phoneNumber): void
    {
        $this->phonenumber = $phoneNumber;
    }

    public function getPhoneNumber(): DDC881PhoneNumber
    {
        return $this->phonenumber;
    }
}
