<?php

namespace DoctrineExtensions\Tests\Types;

use Carbon\Carbon;
use Carbon\CarbonImmutable;
use Doctrine\DBAL\DriverManager;
use Doctrine\DBAL\Platforms\AbstractPlatform;
use Doctrine\DBAL\Types\Type;
use Doctrine\ORM\Configuration;
use Doctrine\ORM\EntityManager;
use Doctrine\ORM\ORMSetup;
use Doctrine\ORM\Tools\SchemaTool;
use DoctrineExtensions\Tests\Entities\CarbonDate;
use DoctrineExtensions\Tests\Entities\CarbonDate as Entity;
use DoctrineExtensions\Types\CarbonDateTimeType;
use DoctrineExtensions\Types\CarbonDateTimeTzType;
use DoctrineExtensions\Types\CarbonDateType;
use DoctrineExtensions\Types\CarbonImmutableDateTimeType;
use DoctrineExtensions\Types\CarbonImmutableDateTimeTzType;
use DoctrineExtensions\Types\CarbonImmutableDateType;
use DoctrineExtensions\Types\CarbonImmutableTimeType;
use DoctrineExtensions\Types\CarbonTimeType;
use PHPUnit\Framework\TestCase;
use Symfony\Component\Cache\Adapter\ArrayAdapter;

use const PHP_VERSION_ID;

/**
 * Test type that maps an SQL DATETIME/TIMESTAMP to a Carbon/Carbon object.
 *
 * @author Steve Lacey <steve@stevelacey.net>
 */
class CarbonDateTest extends TestCase
{
    private $em;

    public static function setUpBeforeClass(): void
    {
        Type::addType('CarbonDate', CarbonDateType::class);
        Type::addType('CarbonDateTime', CarbonDateTimeType::class);
        Type::addType('CarbonDateTimeTz', CarbonDateTimeTzType::class);
        Type::addType('CarbonTime', CarbonTimeType::class);
        Type::addType('CarbonImmutableDate', CarbonImmutableDateType::class);
        Type::addType('CarbonImmutableDateTime', CarbonImmutableDateTimeType::class);
        Type::addType('CarbonImmutableDateTimeTz', CarbonImmutableDateTimeTzType::class);
        Type::addType('CarbonImmutableTime', CarbonImmutableTimeType::class);
    }

    public function setUp(): void
    {
        if (PHP_VERSION_ID < 80000) {
            $config = new Configuration();
            $config->setMetadataDriverImpl(ORMSetup::createDefaultAnnotationDriver([__DIR__ . '/../../Entities']));
        } else {
            $config = ORMSetup::createAttributeMetadataConfiguration([__DIR__ . '/../Entities'], true);
        }

        $config->setMetadataCache(new ArrayAdapter());
        $config->setQueryCache(new ArrayAdapter());
        $config->setProxyDir(__DIR__ . '/Proxies');
        $config->setProxyNamespace('DoctrineExtensions\Tests\PHPUnit\Proxies');
        $config->setAutoGenerateProxyClasses(true);

        $this->em = new EntityManager(
            DriverManager::getConnection([
                'driver' => 'pdo_sqlite',
                'memory' => true,
            ], $config),
            $config
        );

        $schemaTool = new SchemaTool($this->em);
        $schemaTool->dropDatabase();
        $schemaTool->createSchema([
            $this->em->getClassMetadata(CarbonDate::class),
        ]);

        $entity                      = new Entity();
        $entity->id                  = 1;
        $entity->date                = Carbon::createFromDate(2015, 1, 1);
        $entity->datetime            = Carbon::create(2015, 1, 1, 0, 0, 0);
        $entity->datetimeTz          = Carbon::create(2012, 1, 1, 0, 0, 0, 'US/Pacific');
        $entity->time                = Carbon::createFromTime(12, 0, 0, 'Europe/London');
        $entity->dateImmutable       = CarbonImmutable::createFromDate(2015, 1, 1);
        $entity->datetimeImmutable   = CarbonImmutable::create(2015, 1, 1, 0, 0, 0);
        $entity->datetimeTzImmutable = CarbonImmutable::create(2012, 1, 1, 0, 0, 0, 'US/Pacific');
        $entity->timeImmutable       = CarbonImmutable::createFromTime(12, 0, 0, 'Europe/London');
        $this->em->persist($entity);
        $this->em->flush();
    }

    public function testDateGetter(): void
    {
        $entity = $this->em->find(CarbonDate::class, 1);

        $this->assertInstanceOf(Carbon::class, $entity->date);
        $this->assertEquals(
            Carbon::createFromDate(2015, 1, 1, $entity->date->timezone)->format('Y-m-d'),
            $entity->date->format('Y-m-d')
        );
    }

    public function testDateSetter(): void
    {
        $entity       = new Entity();
        $entity->id   = 2;
        $entity->date = Carbon::createFromDate(2015, 1, 1);

        $this->em->persist($entity);
        $this->assertNull($this->em->flush());
    }

    public function testDateTimeGetter(): void
    {
        $entity = $this->em->find(CarbonDate::class, 1);

        $this->assertInstanceOf(Carbon::class, $entity->datetime);
        $this->assertEquals(Carbon::create(2015, 1, 1, 0, 0, 0), $entity->datetime);
    }

    public function testDateTimeSetter(): void
    {
        $entity           = new Entity();
        $entity->id       = 2;
        $entity->datetime = Carbon::create(2015, 1, 1, 0, 0, 0);

        $this->em->persist($entity);
        $this->assertNull($this->em->flush());
    }

    public function testDateTimeTzGetter(): void
    {
        $entity = $this->em->find(CarbonDate::class, 1);

        $this->assertInstanceOf(Carbon::class, $entity->datetimeTz);
        $this->assertEquals(Carbon::create(2012, 1, 1, 0, 0, 0, 'US/Pacific'), $entity->datetimeTz);
    }

    public function testDateTimeTzSetter(): void
    {
        $entity             = new Entity();
        $entity->id         = 2;
        $entity->datetimeTz = Carbon::create(2012, 1, 1, 0, 0, 0, 'US/Pacific');

        $this->em->persist($entity);
        $this->assertNull($this->em->flush());
    }

    public function testTimeGetter(): void
    {
        $entity = $this->em->find(CarbonDate::class, 1);

        $this->assertInstanceOf(Carbon::class, $entity->time);
        $this->assertEquals(Carbon::createFromTime(12, 0, 0, 'Europe/London'), $entity->time);
    }

    public function testTimeSetter(): void
    {
        $entity       = new Entity();
        $entity->id   = 2;
        $entity->time = Carbon::createFromTime(12, 0, 0, 'Europe/London');

        $this->em->persist($entity);
        $this->assertNull($this->em->flush());
    }

    public function testImmutableDateGetter(): void
    {
        $entity = $this->em->find(CarbonDate::class, 1);

        $this->assertInstanceOf(CarbonImmutable::class, $entity->dateImmutable);
        $this->assertEquals(
            CarbonImmutable::createFromDate(2015, 1, 1, $entity->date->timezone)->format('Y-m-d'),
            $entity->date->format('Y-m-d')
        );
    }

    public function testImmutableDateSetter(): void
    {
        $entity       = new Entity();
        $entity->id   = 2;
        $entity->date = CarbonImmutable::createFromDate(2015, 1, 1);

        $this->em->persist($entity);
        $this->assertNull($this->em->flush());
    }

    public function testImmutableDateTimeGetter(): void
    {
        $entity = $this->em->find(CarbonDate::class, 1);

        $this->assertInstanceOf(CarbonImmutable::class, $entity->datetimeImmutable);
        $this->assertEquals(CarbonImmutable::create(2015, 1, 1, 0, 0, 0), $entity->datetime);
    }

    public function testImmutableDateTimeSetter(): void
    {
        $entity           = new Entity();
        $entity->id       = 2;
        $entity->datetime = CarbonImmutable::create(2015, 1, 1, 0, 0, 0);

        $this->em->persist($entity);
        $this->assertNull($this->em->flush());
    }

    public function testImmutableDateTimeTzGetter(): void
    {
        $entity = $this->em->find(CarbonDate::class, 1);

        $this->assertInstanceOf(CarbonImmutable::class, $entity->datetimeTzImmutable);
        $this->assertEquals(CarbonImmutable::create(2012, 1, 1, 0, 0, 0, 'US/Pacific'), $entity->datetimeTz);
    }

    public function testImmutableDateTimeTzSetter(): void
    {
        $entity             = new Entity();
        $entity->id         = 2;
        $entity->datetimeTz = CarbonImmutable::create(2012, 1, 1, 0, 0, 0, 'US/Pacific');

        $this->em->persist($entity);
        $this->assertNull($this->em->flush());
    }

    public function testImmutableTimeGetter(): void
    {
        $entity = $this->em->find(CarbonDate::class, 1);

        $this->assertInstanceOf(CarbonImmutable::class, $entity->timeImmutable);
        $this->assertEquals(CarbonImmutable::createFromTime(12, 0, 0, 'Europe/London'), $entity->time);
    }

    public function testImmutableTimeSetter(): void
    {
        $entity       = new Entity();
        $entity->id   = 2;
        $entity->time = CarbonImmutable::createFromTime(12, 0, 0, 'Europe/London');

        $this->em->persist($entity);
        $this->assertNull($this->em->flush());
    }

    /** @dataProvider typeProvider */
    public function testTypesThatMapToAlreadyMappedDatabaseTypesRequireCommentHint($type): void
    {
        $platform = $this->createMock(AbstractPlatform::class);

        $this->assertTrue(Type::getType($type)->requiresSQLCommentHint($platform));
    }

    /** @return array<int, array<int, string>> */
    public static function typeProvider(): array
    {
        return [
            ['CarbonDate'],
            ['CarbonDateTime'],
            ['CarbonDateTimeTz'],
            ['CarbonTime'],
            ['CarbonImmutableDate'],
            ['CarbonImmutableDateTime'],
            ['CarbonImmutableDateTimeTz'],
            ['CarbonImmutableTime'],
        ];
    }
}
