<?php

declare(strict_types=1);

namespace App\Tests\Unit\Shared\Doctrine;

use App\Shared\Application\CurrentUserProviderInterface;
use App\Shared\Domain\Contract\BlamableInterface;
use App\Shared\Domain\Contract\TimestampableInterface;
use App\Shared\Domain\Contract\UserInterface;
use App\Shared\Domain\Trait\TimestampableBlamableTrait;
use App\Shared\Infrastructure\Doctrine\TimestampableBlamableSubscriber;
use DateTimeImmutable;
use PHPUnit\Framework\TestCase;
use stdClass;

/**
 * @internal
 */
final class TimestampableBlamableSubscriberTest extends TestCase
{
    public function testApplyOnCreateSetsTimestampsAndAuthor(): void
    {
        $user       = $this->makeUser(7);
        $subscriber = new TimestampableBlamableSubscriber($this->providerReturning($user));
        $entity     = $this->makeEntity();

        $subscriber->applyOnCreate($entity);

        self::assertInstanceOf(DateTimeImmutable::class, $entity->getCreatedAt());
        self::assertInstanceOf(DateTimeImmutable::class, $entity->getUpdatedAt());
        self::assertSame($user, $entity->getCreatedBy());
        self::assertSame($user, $entity->getUpdatedBy());
    }

    public function testApplyOnUpdateLeavesCreatedUntouched(): void
    {
        $creator = $this->makeUser(1);
        $editor  = $this->makeUser(2);
        $entity  = $this->makeEntity();

        new TimestampableBlamableSubscriber($this->providerReturning($creator))->applyOnCreate($entity);
        $createdAt = $entity->getCreatedAt();

        new TimestampableBlamableSubscriber($this->providerReturning($editor))->applyOnUpdate($entity);

        self::assertSame($createdAt, $entity->getCreatedAt());
        self::assertSame($creator, $entity->getCreatedBy());
        self::assertSame($editor, $entity->getUpdatedBy());
    }

    public function testApplyOnCreateIgnoresNonTimestampableEntities(): void
    {
        $subscriber = new TimestampableBlamableSubscriber($this->providerReturning(null));

        // Must not throw.
        $subscriber->applyOnCreate(new stdClass());
        $this->addToAssertionCount(1);
    }

    private function providerReturning(?UserInterface $user): CurrentUserProviderInterface
    {
        return new class($user) implements CurrentUserProviderInterface {
            public function __construct(private ?UserInterface $user) {}

            public function getCurrentUser(): ?UserInterface
            {
                return $this->user;
            }
        };
    }

    private function makeUser(int $id): UserInterface
    {
        return new class($id) implements UserInterface {
            public function __construct(private int $id) {}

            public function getId(): ?int
            {
                return $this->id;
            }

            public function getUserIdentifier(): string
            {
                return 'user-'.$this->id;
            }

            public function getUsername(): ?string
            {
                return 'user-'.$this->id;
            }

            /** @return list<string> */
            public function getRoles(): array
            {
                return ['ROLE_USER'];
            }

            public function getFirstName(): ?string
            {
                return null;
            }

            public function getLastName(): ?string
            {
                return null;
            }

            public function getAvatarUrl(): ?string
            {
                return null;
            }

            public function getIsEmployee(): bool
            {
                return false;
            }

            public function getEffectivePermissions(): array
            {
                return [];
            }
        };
    }

    private function makeEntity(): object
    {
        return new class implements TimestampableInterface, BlamableInterface {
            use TimestampableBlamableTrait;
        };
    }
}
