<?php

declare(strict_types=1);

namespace Doctrine\Tests\ORM\Functional\Ticket;

use DateTime;
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\HasLifecycleCallbacks;
use Doctrine\ORM\Mapping\Id;
use Doctrine\ORM\Mapping\JoinColumn;
use Doctrine\ORM\Mapping\OneToMany;
use Doctrine\ORM\Mapping\OneToOne;
use Doctrine\ORM\Mapping\PrePersist;
use Doctrine\ORM\Mapping\PreUpdate;
use Doctrine\ORM\Mapping\Table;
use Doctrine\ORM\Mapping\UniqueConstraint;
use Doctrine\Tests\OrmFunctionalTestCase;

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

        $this->createSchemaForModels(
            DDC345User::class,
            DDC345Group::class,
            DDC345Membership::class,
        );
    }

    public function testTwoIterateHydrations(): void
    {
        // Create User
        $user       = new DDC345User();
        $user->name = 'Test User';
        $this->_em->persist($user); // $em->flush() does not change much here

        // Create Group
        $group       = new DDC345Group();
        $group->name = 'Test Group';
        $this->_em->persist($group); // $em->flush() does not change much here

        $membership        = new DDC345Membership();
        $membership->group = $group;
        $membership->user  = $user;
        $membership->state = 'active';

        //$this->_em->persist($membership); // COMMENT OUT TO SEE BUG
        /*
        This should be not necessary, but without, its PrePersist is called twice,
        $membership seems to be persisted twice, but all properties but the
        ones set by LifecycleCallbacks are deleted.
        */

        $user->memberships->add($membership);
        $group->memberships->add($membership);

        $this->_em->flush();

        self::assertEquals(1, $membership->prePersistCallCount);
        self::assertEquals(0, $membership->preUpdateCallCount);
        self::assertInstanceOf(DateTime::class, $membership->updated);
    }
}

#[Entity]
class DDC345User
{
    /** @var int */
    #[Id]
    #[Column(type: 'integer')]
    #[GeneratedValue]
    public $id;

    /** @var string */
    #[Column(type: 'string', length: 255)]
    public $name;

    /** @psalm-var Collection<int, DDC345Membership> */
    #[OneToMany(targetEntity: 'DDC345Membership', mappedBy: 'user', cascade: ['persist'])]
    public $memberships;

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

#[Entity]
class DDC345Group
{
    /** @var int */
    #[Id]
    #[Column(type: 'integer')]
    #[GeneratedValue]
    public $id;

    /** @var string */
    #[Column(type: 'string', length: 255)]
    public $name;

    /** @psalm-var Collection<int, DDC345Membership> */
    #[OneToMany(targetEntity: 'DDC345Membership', mappedBy: 'group', cascade: ['persist'])]
    public $memberships;

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

#[Table(name: 'ddc345_memberships')]
#[UniqueConstraint(name: 'ddc345_memship_fks', columns: ['user_id', 'group_id'])]
#[Entity]
#[HasLifecycleCallbacks]
class DDC345Membership
{
    /** @var int */
    #[Id]
    #[Column(type: 'integer')]
    #[GeneratedValue]
    public $id;

    /** @var DDC345User */
    #[OneToOne(targetEntity: 'DDC345User', inversedBy: 'memberships')]
    #[JoinColumn(name: 'user_id', referencedColumnName: 'id', nullable: false)]
    public $user;

    /** @var DDC345Group */
    #[OneToOne(targetEntity: 'DDC345Group', inversedBy: 'memberships')]
    #[JoinColumn(name: 'group_id', referencedColumnName: 'id', nullable: false)]
    public $group;

    /** @var string */
    #[Column(type: 'string', length: 255)]
    public $state;

    /** @var DateTime */
    #[Column(type: 'datetime')]
    public $updated;

    /** @var int */
    public $prePersistCallCount = 0;

    /** @var int */
    public $preUpdateCallCount = 0;

    #[PrePersist]
    public function doStuffOnPrePersist(): void
    {
        //echo "***** PrePersist\n";
        ++$this->prePersistCallCount;
        $this->updated = new DateTime();
    }

    #[PreUpdate]
    public function doStuffOnPreUpdate(): void
    {
        //echo "***** PreUpdate\n";
        ++$this->preUpdateCallCount;
        $this->updated = new DateTime();
    }
}
