<?php

declare(strict_types=1);

namespace Apexl\Tests\User\UnitTests;

use apexl\Io\includes\Entity;
use apexl\Io\interfaces\GlobalDataInterface;
use apexl\Io\modules\user\hooks\AddCreatedByToEntitiesOnSave;
use apexl\Io\modules\user\interfaces\MasqueradeServiceInterface;
use apexl\Io\modules\user\services\CurrentUserFactory;
use PHPUnit\Framework\Attributes\Test;
use PHPUnit\Framework\TestCase;
use ReflectionClass;
use ReflectionProperty;

/**
 * `created_by` is first-write-wins: the hook stamps the current user only when
 * the caller has not already said who created the row. Uses plain TestCase —
 * these assertions need no container or database, and io-user's UnitTest base
 * cannot bootstrap standalone (it expects a host app's .env).
 */
final class AddCreatedByToEntitiesOnSaveTest extends TestCase
{
    /** Entity built without its constructor: no container, no Vault, no DESCRIBE. */
    private function entityWithDataFields(string ...$fields): Entity
    {
        $entity = new ReflectionClass(Entity::class)->newInstanceWithoutConstructor();

        $property = new ReflectionProperty(Entity::class, 'dataFields');
        $property->setValue(
            $entity,
            array_map(static fn(string $field): object => (object) ['Field' => $field], $fields),
        );

        return $entity;
    }

    /**
     * A hook with no current user. Hydrating a real userEntity here would drag in
     * the cast/relation graph and a container; the "stamps the real user id" case
     * therefore lives in CreatedByIsStampedOnFirstStoreTest, against a real store.
     */
    private function hook(): AddCreatedByToEntitiesOnSave
    {
        $globalData = $this->createMock(GlobalDataInterface::class);
        $globalData->method('getData')->willReturn([]);

        $masquerade = $this->createMock(MasqueradeServiceInterface::class);
        $masquerade->method('isMasquerading')->willReturn(false);

        return new AddCreatedByToEntitiesOnSave(new CurrentUserFactory($globalData, $masquerade));
    }

    #[Test]
    public function stampsZeroWhenThereIsNoCurrentUser(): void
    {
        $data = $this->hook()(['name' => 'Acme'], $this->entityWithDataFields('name', 'created_by'));

        $this->assertSame(0, $data['created_by']);
    }

    #[Test]
    public function anExplicitlyAssignedCreatedBySurvives(): void
    {
        $data = $this->hook()(
            ['name' => 'Acme', 'created_by' => 42],
            $this->entityWithDataFields('name', 'created_by'),
        );

        $this->assertSame(42, $data['created_by'], 'created_by is immutable provenance: the caller wins.');
    }

    #[Test]
    public function anExplicitNullCreatedBySurvives(): void
    {
        // `array_key_exists`, not `isset`: consumers write null on purpose to
        // mean "no human actor". `isset` would overwrite it with the current user.
        $data = $this->hook()(
            ['name' => 'Acme', 'created_by' => null],
            $this->entityWithDataFields('name', 'created_by'),
        );

        $this->assertArrayHasKey('created_by', $data);
        $this->assertNull($data['created_by'], 'A deliberate null means "no human actor" and is a value.');
    }

    #[Test]
    public function anEntityWithoutACreatedByColumnIsUntouched(): void
    {
        $data = $this->hook()(['name' => 'Acme'], $this->entityWithDataFields('name'));

        $this->assertSame(['name' => 'Acme'], $data);
    }
}
