<?php

declare(strict_types=1);

namespace Doctrine\Tests\ORM\Functional\Ticket;

use BadMethodCallException;
use Closure;
use Doctrine\DBAL\Cache\QueryCacheProfile;
use Doctrine\DBAL\Connection;
use Doctrine\DBAL\Result;
use Doctrine\DBAL\Statement;
use Doctrine\ORM\EntityManager;
use Doctrine\ORM\Mapping\Column;
use Doctrine\ORM\Mapping\DiscriminatorMap;
use Doctrine\ORM\Mapping\Entity;
use Doctrine\ORM\Mapping\GeneratedValue;
use Doctrine\ORM\Mapping\Id;
use Doctrine\ORM\Mapping\InheritanceType;
use Doctrine\ORM\Tools\ToolsException;
use Doctrine\Tests\OrmFunctionalTestCase;

use function call_user_func_array;
use function debug_backtrace;

use const PHP_INT_MAX;

/**
 * @group DDC-3634
 */
class DDC3634Test extends OrmFunctionalTestCase
{
    protected function setUp(): void
    {
        parent::setUp();

        $metadata = $this->_em->getClassMetadata(DDC3634Entity::class);

        if (! $metadata->idGenerator->isPostInsertGenerator()) {
            self::markTestSkipped('Need a post-insert ID generator in order to make this test work correctly');
        }

        try {
            $this->_schemaTool->createSchema([
                $metadata,
                $this->_em->getClassMetadata(DDC3634JTIBaseEntity::class),
                $this->_em->getClassMetadata(DDC3634JTIChildEntity::class),
            ]);
        } catch (ToolsException $e) {
            // schema already in place
        }
    }

    public function testSavesVeryLargeIntegerAutoGeneratedValue(): void
    {
        $veryLargeId = PHP_INT_MAX . PHP_INT_MAX;

        $entityManager = EntityManager::create(
            new DDC3634LastInsertIdMockingConnection($veryLargeId, $this->_em->getConnection()),
            $this->_em->getConfiguration()
        );

        $entity = new DDC3634Entity();

        $entityManager->persist($entity);
        $entityManager->flush();

        self::assertSame($veryLargeId, $entity->id);
    }

    public function testSavesIntegerAutoGeneratedValueAsString(): void
    {
        $entity = new DDC3634Entity();

        $this->_em->persist($entity);
        $this->_em->flush();

        self::assertIsString($entity->id);
    }

    public function testSavesIntegerAutoGeneratedValueAsStringWithJoinedInheritance(): void
    {
        $entity = new DDC3634JTIChildEntity();

        $this->_em->persist($entity);
        $this->_em->flush();

        self::assertIsString($entity->id);
    }
}

/** @Entity */
class DDC3634Entity
{
    /**
     * @var int
     * @Id
     * @Column(type="bigint")
     * @GeneratedValue(strategy="AUTO")
     */
    public $id;
}

/**
 * @Entity
 * @InheritanceType("JOINED")
 * @DiscriminatorMap({
 *  DDC3634JTIBaseEntity::class  = DDC3634JTIBaseEntity::class,
 *  DDC3634JTIChildEntity::class = DDC3634JTIChildEntity::class,
 * })
 */
class DDC3634JTIBaseEntity
{
    /**
     * @var int
     * @Id
     * @Column(type="bigint")
     * @GeneratedValue(strategy="AUTO")
     */
    public $id;
}

/** @Entity */
class DDC3634JTIChildEntity extends DDC3634JTIBaseEntity
{
}

class DDC3634LastInsertIdMockingConnection extends Connection
{
    /** @var Connection */
    private $realConnection;

    /** @var int|string */
    private $identifier;

    /**
     * @param int|string $identifier
     */
    public function __construct($identifier, Connection $realConnection)
    {
        $this->realConnection = $realConnection;
        $this->identifier     = $identifier;
    }

    /**
     * @return mixed
     */
    private function forwardCall()
    {
        $trace = debug_backtrace(0, 2)[1];

        return call_user_func_array([$this->realConnection, $trace['function']], $trace['args']);
    }

    /** {@inheritDoc} */
    public function getParams()
    {
        return $this->forwardCall();
    }

    /** {@inheritDoc} */
    public function getDatabase()
    {
        return $this->forwardCall();
    }

    /** {@inheritDoc} */
    public function getHost()
    {
        return $this->forwardCall();
    }

    /** {@inheritDoc} */
    public function getPort()
    {
        return $this->forwardCall();
    }

    /** {@inheritDoc} */
    public function getUsername()
    {
        return $this->forwardCall();
    }

    /** {@inheritDoc} */
    public function getPassword()
    {
        return $this->forwardCall();
    }

    /** {@inheritDoc} */
    public function getDriver()
    {
        return $this->forwardCall();
    }

    /** {@inheritDoc} */
    public function getConfiguration()
    {
        return $this->forwardCall();
    }

    /** {@inheritDoc} */
    public function getEventManager()
    {
        return $this->forwardCall();
    }

    /** {@inheritDoc} */
    public function getDatabasePlatform()
    {
        return $this->forwardCall();
    }

    /** {@inheritDoc} */
    public function getExpressionBuilder()
    {
        return $this->forwardCall();
    }

    /** {@inheritDoc} */
    public function connect()
    {
        return $this->forwardCall();
    }

    /** {@inheritDoc} */
    public function isAutoCommit()
    {
        return $this->forwardCall();
    }

    /** {@inheritDoc} */
    public function setAutoCommit($autoCommit)
    {
        $this->forwardCall();
    }

    /** {@inheritDoc} */
    public function setFetchMode($fetchMode)
    {
        $this->forwardCall();
    }

    /** {@inheritDoc} */
    public function fetchAssoc($statement, array $params = [], array $types = [])
    {
        return $this->forwardCall();
    }

    /** {@inheritDoc} */
    public function fetchArray($statement, array $params = [], array $types = [])
    {
        return $this->forwardCall();
    }

    /** {@inheritDoc} */
    public function fetchColumn($statement, array $params = [], $column = 0, array $types = [])
    {
        return $this->forwardCall();
    }

    /** {@inheritDoc} */
    public function isConnected()
    {
        return $this->forwardCall();
    }

    /** {@inheritDoc} */
    public function isTransactionActive()
    {
        return $this->forwardCall();
    }

    /** {@inheritDoc} */
    public function delete($tableExpression, array $identifier, array $types = [])
    {
        return $this->forwardCall();
    }

    /** {@inheritDoc} */
    public function close()
    {
        $this->forwardCall();
    }

    /** {@inheritDoc} */
    public function setTransactionIsolation($level)
    {
        return $this->forwardCall();
    }

    /** {@inheritDoc} */
    public function getTransactionIsolation()
    {
        return $this->forwardCall();
    }

    /** {@inheritDoc} */
    public function update($tableExpression, array $data, array $identifier, array $types = [])
    {
        return $this->forwardCall();
    }

    /** {@inheritDoc} */
    public function insert($tableExpression, array $data, array $types = [])
    {
        return $this->forwardCall();
    }

    /** {@inheritDoc} */
    public function quoteIdentifier($str)
    {
        return $this->forwardCall();
    }

    /** {@inheritDoc} */
    public function quote($input, $type = null)
    {
        return $this->forwardCall();
    }

    /** {@inheritDoc} */
    public function fetchAll($sql, array $params = [], $types = [])
    {
        return $this->forwardCall();
    }

    /** {@inheritDoc} */
    public function prepare($statement): Statement
    {
        return $this->forwardCall();
    }

    /** {@inheritDoc} */
    public function executeQuery($query, array $params = [], $types = [], ?QueryCacheProfile $qcp = null): Result
    {
        return $this->forwardCall();
    }

    /** {@inheritDoc} */
    public function executeCacheQuery($query, $params, $types, QueryCacheProfile $qcp): Result
    {
        return $this->forwardCall();
    }

    /** {@inheritDoc} */
    public function project($query, array $params, Closure $function)
    {
        return $this->forwardCall();
    }

    /** {@inheritDoc} */
    public function query($sql = null): Result
    {
        return $this->forwardCall();
    }

    /** {@inheritDoc} */
    public function executeUpdate($query, array $params = [], array $types = []): int
    {
        throw new BadMethodCallException('Call to deprecated method.');
    }

    /** {@inheritDoc} */
    public function executeStatement($query, array $params = [], array $types = []): int
    {
        return $this->forwardCall();
    }

    /** {@inheritDoc} */
    public function exec($statement): int
    {
        return $this->forwardCall();
    }

    /** {@inheritDoc} */
    public function getTransactionNestingLevel()
    {
        return $this->forwardCall();
    }

    /** {@inheritDoc} */
    public function errorCode()
    {
        return $this->forwardCall();
    }

    /** {@inheritDoc} */
    public function errorInfo()
    {
        return $this->forwardCall();
    }

    /** {@inheritDoc} */
    public function lastInsertId($seqName = null)
    {
        return $this->identifier;
    }

    /** {@inheritDoc} */
    public function transactional(Closure $func)
    {
        return $this->forwardCall();
    }

    /** {@inheritDoc} */
    public function setNestTransactionsWithSavepoints($nestTransactionsWithSavepoints)
    {
        return $this->forwardCall();
    }

    /** {@inheritDoc} */
    public function getNestTransactionsWithSavepoints()
    {
        return $this->forwardCall();
    }

    /** {@inheritDoc} */
    protected function _getNestedTransactionSavePointName()
    {
        return $this->forwardCall();
    }

    /** {@inheritDoc} */
    public function beginTransaction()
    {
        return $this->forwardCall();
    }

    /** {@inheritDoc} */
    public function commit()
    {
        return $this->forwardCall();
    }

    /** {@inheritDoc} */
    public function rollBack()
    {
        return $this->forwardCall();
    }

    /** {@inheritDoc} */
    public function createSavepoint($savepoint)
    {
        return $this->forwardCall();
    }

    /** {@inheritDoc} */
    public function releaseSavepoint($savepoint)
    {
        return $this->forwardCall();
    }

    /** {@inheritDoc} */
    public function rollbackSavepoint($savepoint)
    {
        return $this->forwardCall();
    }

    /** {@inheritDoc} */
    public function getWrappedConnection()
    {
        return $this->forwardCall();
    }

    /** {@inheritDoc} */
    public function getSchemaManager()
    {
        return $this->forwardCall();
    }

    /** {@inheritDoc} */
    public function setRollbackOnly()
    {
        $this->forwardCall();
    }

    /** {@inheritDoc} */
    public function isRollbackOnly()
    {
        return $this->forwardCall();
    }

    /** {@inheritDoc} */
    public function convertToDatabaseValue($value, $type)
    {
        return $this->forwardCall();
    }

    /** {@inheritDoc} */
    public function convertToPHPValue($value, $type)
    {
        return $this->forwardCall();
    }

    /** {@inheritDoc} */
    public function resolveParams(array $params, array $types)
    {
        return $this->forwardCall();
    }

    /** {@inheritDoc} */
    public function createQueryBuilder()
    {
        return $this->forwardCall();
    }

    /** {@inheritDoc} */
    public function ping()
    {
        return $this->forwardCall();
    }
}
