<?php

declare(strict_types=1);

namespace FeatureTests\Entity;

use apexl\Io\operators\entityDatabaseOperator;
use apexl\Io\TestInfrastructure\FeatureTest;
use Apexl\Tests\Core\Infrastructure\NaturalKeyTestEntity;
use PHPUnit\Framework\Attributes\Test;

/**
 * entityDatabaseOperator::store() re-hydrates the entity from the row it just
 * INSERTed. For a natural (non auto-increment) primary key it used to reload
 * whatever `ORDER BY <pk> DESC LIMIT 1` returned instead — a DIFFERENT row.
 * That is how a freshly stored refresh-token row came back holding another
 * user's token.
 */
class NaturalPrimaryKeyStoreTest extends FeatureTest
{
    private string $naturalKeyTable;
    private string $autoIncrementTable;

    #[Test]
    public function operatorRehydratesTheNaturalKeyRowItJustWrote(): void
    {
        $operator = new entityDatabaseOperator($this->naturalKeyTable, 'user_key');

        // A higher key already exists — the row `ORDER BY user_key DESC` finds.
        $operator->store(['user_key' => 14, 'token' => 'token-for-14']);

        $stored = $operator->store(['user_key' => 13, 'token' => 'token-for-13']);

        $this->assertSame(13, (int) $stored['data']['user_key']);
        $this->assertSame('token-for-13', $stored['data']['token']);
    }

    #[Test]
    public function storingANaturalKeyEntityLeavesItHoldingItsOwnData(): void
    {
        $existing = new NaturalKeyTestEntity();
        $existing->user_key = 14;
        $existing->token = 'token-for-14';
        $existing->store();

        $entity = new NaturalKeyTestEntity();
        $entity->user_key = 13;
        $entity->token = 'token-for-13';
        $entity->store();

        $this->assertSame(13, (int) $entity->user_key);
        $this->assertSame('token-for-13', $entity->token);
    }

    /**
     * lastInsertId() is per-connection and is NOT reset by an insert into a
     * table with no auto-increment column, so an earlier insert elsewhere leaves
     * a stale id behind that must not be mistaken for this row's key.
     */
    #[Test]
    public function aStaleLastInsertIdCannotPoisonANaturalKeyInsert(): void
    {
        $autoIncrement = new entityDatabaseOperator($this->autoIncrementTable);
        $autoIncrement->store(['name' => 'some other table']);

        $operator = new entityDatabaseOperator($this->naturalKeyTable, 'user_key');
        $stored = $operator->store(['user_key' => 7, 'token' => 'token-for-7']);

        $this->assertSame(7, (int) $stored['data']['user_key']);
        $this->assertSame('token-for-7', $stored['data']['token']);
    }

    /**
     * The auto-increment path is unchanged: the generated id is read from the
     * connection-scoped lastInsertId(), which sees this connection's
     * as-yet-uncommitted insert (the fix released in 3.6.9).
     */
    #[Test]
    public function autoIncrementInsertsStillRehydrateTheGeneratedRow(): void
    {
        $operator = new entityDatabaseOperator($this->autoIncrementTable);

        $first = $operator->store(['name' => 'first']);
        $second = $operator->store(['name' => 'second']);

        $this->assertSame('first', $first['data']['name']);
        $this->assertSame('second', $second['data']['name']);
        $this->assertGreaterThan((int) $first['data']['id'], (int) $second['data']['id']);
    }

    protected function setUp(): void
    {
        parent::setUp();

        $uniqId = uniqid();
        $this->naturalKeyTable = "natural_key_store__$uniqId";
        $this->autoIncrementTable = "auto_increment_store__$uniqId";
        NaturalKeyTestEntity::$tableName = $this->naturalKeyTable;

        self::$db->executeSql(
            <<<SQL
            create table `$this->naturalKeyTable`
            (
                user_key int(11) unsigned not null primary key,
                token varchar(255) not null
            )
            SQL
        );

        self::$db->executeSql(
            <<<SQL
            create table `$this->autoIncrementTable`
            (
                id int(11) unsigned auto_increment primary key,
                name varchar(255) not null
            )
            SQL
        );
    }

    protected function tearDown(): void
    {
        parent::tearDown();

        self::$db->executeSql("drop table `$this->naturalKeyTable`");
        self::$db->executeSql("drop table `$this->autoIncrementTable`");
        NaturalKeyTestEntity::$tableName = null;
    }
}
